client/comments: rework comments appearance and UX
This commit is contained in:
parent
f0573be715
commit
fe0ba63f19
|
@ -1,51 +1,8 @@
|
||||||
@import colors
|
@import colors
|
||||||
|
$comment-header-background-color = $top-navigation-color
|
||||||
|
$comment-border-color = #DDD
|
||||||
|
|
||||||
.comment-form-container
|
.comment-container
|
||||||
&:not(.editing)
|
|
||||||
.tabs nav
|
|
||||||
display: none
|
|
||||||
.tabs .edit.tab
|
|
||||||
display: none
|
|
||||||
.comment-content
|
|
||||||
margin-left: 0.5em
|
|
||||||
&.editing
|
|
||||||
.tab:not(.active)
|
|
||||||
display: none
|
|
||||||
.tabs-wrapper
|
|
||||||
background: $active-tab-background-color
|
|
||||||
padding: 0.3em
|
|
||||||
.tab-wrapper[data-tab='preview']
|
|
||||||
background: $window-color
|
|
||||||
.tab.preview
|
|
||||||
padding: 1em
|
|
||||||
.tab.edit
|
|
||||||
textarea
|
|
||||||
resize: vertical
|
|
||||||
width: 100%
|
|
||||||
max-height: 80vh
|
|
||||||
box-sizing: padding-box
|
|
||||||
vertical-align: top /* ghost margin on chrome */
|
|
||||||
|
|
||||||
form
|
|
||||||
width: auto
|
|
||||||
margin: 0
|
|
||||||
&:after
|
|
||||||
display: block
|
|
||||||
height: 1px
|
|
||||||
content: ' '
|
|
||||||
clear: both
|
|
||||||
|
|
||||||
nav
|
|
||||||
vertical-align: middle !important
|
|
||||||
&.buttons
|
|
||||||
margin: 0 0.3em 0.5em 0 !important
|
|
||||||
float: left
|
|
||||||
&.actions
|
|
||||||
float: left
|
|
||||||
margin: 0.3em 0 0.5em 0 !important
|
|
||||||
|
|
||||||
|
|
||||||
.comment
|
|
||||||
margin: 0 0 1em 0
|
margin: 0 0 1em 0
|
||||||
padding: 0
|
padding: 0
|
||||||
display: -webkit-flex
|
display: -webkit-flex
|
||||||
|
@ -63,25 +20,67 @@
|
||||||
a
|
a
|
||||||
display: inline-block
|
display: inline-block
|
||||||
|
|
||||||
.body
|
nav:not(.active), .tab:not(.active)
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.comment
|
||||||
|
border: 1px solid $comment-border-color
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
|
|
||||||
header
|
header
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
line-height: 16pt
|
font-size: 95%
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
margin-bottom: 0.5em
|
position: relative
|
||||||
background: $top-navigation-color
|
background: $comment-header-background-color
|
||||||
padding: 0.2em 0.5em
|
border-bottom: 1px solid $comment-border-color
|
||||||
|
|
||||||
.nickname, .date, .score-container, .edit
|
nav.edit
|
||||||
|
padding: 0.33em 1em 0 1em
|
||||||
|
ul
|
||||||
|
list-style-type: none
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
li
|
||||||
|
display: inline-block
|
||||||
|
margin: 0 0 -1px 0
|
||||||
|
a
|
||||||
|
padding: 0.25em 1em
|
||||||
|
&.active
|
||||||
|
background: $window-color
|
||||||
|
border: 1px solid $comment-border-color
|
||||||
|
border-bottom: none
|
||||||
|
|
||||||
|
nav.readonly
|
||||||
|
padding: 0.33em 1em
|
||||||
|
|
||||||
|
&:before
|
||||||
|
position: absolute
|
||||||
|
display: block
|
||||||
|
content: ' '
|
||||||
|
width: 0
|
||||||
|
height: 0
|
||||||
|
left: -1.5em
|
||||||
|
top: calc(50% - 0.75em)
|
||||||
|
border: 0.75em solid transparent
|
||||||
|
border-right: 0.75em solid darken($comment-border-color, 10%)
|
||||||
|
|
||||||
|
&:after
|
||||||
|
position: absolute
|
||||||
|
display: block
|
||||||
|
content: ' '
|
||||||
|
width: 0
|
||||||
|
height: 0
|
||||||
|
left: calc(-1.5em + 1px)
|
||||||
|
top: calc(50% - 0.75em)
|
||||||
|
border: 0.75em solid transparent
|
||||||
|
border-right: 0.75em solid $comment-header-background-color
|
||||||
|
|
||||||
|
.date, .score-container, .edit
|
||||||
margin-right: 2em
|
margin-right: 2em
|
||||||
.date, .score-container, .edit, .delete
|
|
||||||
font-size: 95%
|
|
||||||
.edit, .delete, .score-container a, .nickname a
|
.edit, .delete, .score-container a, .nickname a
|
||||||
&:not(.inactive)
|
&:not(.inactive)
|
||||||
color: mix($main-color, $inactive-tab-text-color)
|
color: mix($main-color, $inactive-tab-text-color)
|
||||||
.edit, .delete
|
|
||||||
font-size: 80%
|
|
||||||
|
|
||||||
i
|
i
|
||||||
margin-right: 0.3em
|
margin-right: 0.3em
|
||||||
|
@ -96,6 +95,19 @@
|
||||||
display: inline-block
|
display: inline-block
|
||||||
width: 2em
|
width: 2em
|
||||||
|
|
||||||
|
.body
|
||||||
|
width: auto
|
||||||
|
margin: 1em
|
||||||
|
|
||||||
|
.keep-height
|
||||||
|
position: relative
|
||||||
|
textarea
|
||||||
|
position: absolute
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
.tab.edit
|
||||||
|
min-height: 150px
|
||||||
|
|
||||||
.messages
|
.messages
|
||||||
margin: 1em 0
|
margin: 1em 0
|
||||||
|
|
||||||
|
@ -118,9 +130,6 @@
|
||||||
white-space: pre
|
white-space: pre
|
||||||
word-wrap: normal
|
word-wrap: normal
|
||||||
|
|
||||||
p:first-child
|
|
||||||
margin-top: 0
|
|
||||||
|
|
||||||
.spoiler
|
.spoiler
|
||||||
background: #eee
|
background: #eee
|
||||||
color: #eee
|
color: #eee
|
||||||
|
@ -140,5 +149,5 @@
|
||||||
background: #fafafa
|
background: #fafafa
|
||||||
color: #444
|
color: #444
|
||||||
|
|
||||||
blockquote :last-child
|
:last-child
|
||||||
margin-bottom: 0
|
margin-bottom: 0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.comments>ul
|
.comments>ul
|
||||||
list-style-type: none
|
list-style-type: none
|
||||||
margin: 0 0 2em 0
|
margin: 0
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
|
@ -1,57 +1,85 @@
|
||||||
<div class='comment'>
|
<div class='comment-container'>
|
||||||
<div class='avatar'>
|
<div class='avatar'>
|
||||||
<% if (ctx.comment.user && ctx.comment.user.name && ctx.canViewUsers) { %>
|
<% if (ctx.user && ctx.user.name && ctx.canViewUsers) { %>
|
||||||
<a href='/user/<%- encodeURIComponent(ctx.comment.user.name) %>'>
|
<a href='/user/<%- encodeURIComponent(ctx.user.name) %>'>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<%= ctx.makeThumbnail(ctx.comment.user ? ctx.comment.user.avatarUrl : null) %>
|
<%= ctx.makeThumbnail(ctx.user ? ctx.user.avatarUrl : null) %>
|
||||||
|
|
||||||
<% if (ctx.comment.user && ctx.comment.user.name && ctx.canViewUsers) { %>
|
<% if (ctx.user && ctx.user.name && ctx.canViewUsers) { %>
|
||||||
</a>
|
</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='body'>
|
<div class='comment'>
|
||||||
<header><%
|
<header>
|
||||||
%><span class='nickname'><%
|
<nav class='edit tabs'>
|
||||||
%><% if (ctx.comment.user && ctx.comment.user.name && ctx.canViewUsers) { %><%
|
<ul>
|
||||||
%><a href='/user/<%- encodeURIComponent(ctx.comment.user.name) %>'><%
|
<li class='edit'><a href>Write</a></li>
|
||||||
%><% } %><%
|
<li class='preview'><a href>Preview</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
%><%- ctx.comment.user ? ctx.comment.user.name : 'Deleted user' %><%
|
<nav class='readonly'><%
|
||||||
|
%><strong><span class='nickname'><%
|
||||||
|
%><% if (ctx.user && ctx.user.name && ctx.canViewUsers) { %><%
|
||||||
|
%><a href='/user/<%- encodeURIComponent(ctx.user.name) %>'><%
|
||||||
|
%><% } %><%
|
||||||
|
|
||||||
%><% if (ctx.comment.user && ctx.comment.user.name && ctx.canViewUsers) { %><%
|
%><%- ctx.user ? ctx.user.name : 'Deleted user' %><%
|
||||||
|
|
||||||
|
%><% if (ctx.user && ctx.user.name && ctx.canViewUsers) { %><%
|
||||||
|
%></a><%
|
||||||
|
%><% } %><%
|
||||||
|
%></span></strong>
|
||||||
|
|
||||||
|
<span class='date'><%
|
||||||
|
%>commented <%= ctx.makeRelativeTime(ctx.comment ? ctx.comment.creationTime : null) %><%
|
||||||
|
%></span><%
|
||||||
|
|
||||||
|
%><wbr><%
|
||||||
|
|
||||||
|
%><span class='score-container'></span><%
|
||||||
|
|
||||||
|
%><wbr><%
|
||||||
|
|
||||||
|
%><% if (ctx.canEditComment) { %><%
|
||||||
|
%><a href class='edit'><%
|
||||||
|
%><i class='fa fa-pencil'></i> edit<%
|
||||||
%></a><%
|
%></a><%
|
||||||
%><% } %><%
|
%><% } %><%
|
||||||
%></span><%
|
|
||||||
|
|
||||||
%><wbr><%
|
%><wbr><%
|
||||||
|
|
||||||
%><span class='date'><%
|
%><% if (ctx.canDeleteComment) { %><%
|
||||||
%><%= ctx.makeRelativeTime(ctx.comment.creationTime) %><%
|
%><a href class='delete'><%
|
||||||
%></span><%
|
%><i class='fa fa-remove'></i> delete<%
|
||||||
|
%></a><%
|
||||||
%><wbr><%
|
%><% } %><%
|
||||||
|
%></nav><%
|
||||||
%><span class='score-container'></span><%
|
|
||||||
|
|
||||||
%><wbr><%
|
|
||||||
|
|
||||||
%><% if (ctx.canEditComment) { %><%
|
|
||||||
%><a href class='edit'><%
|
|
||||||
%><i class='fa fa-pencil'></i> edit<%
|
|
||||||
%></a><%
|
|
||||||
%><% } %><%
|
|
||||||
|
|
||||||
%><wbr><%
|
|
||||||
|
|
||||||
%><% if (ctx.canDeleteComment) { %><%
|
|
||||||
%><a href class='delete'><%
|
|
||||||
%><i class='fa fa-remove'></i> delete<%
|
|
||||||
%></a><%
|
|
||||||
%><% } %><%
|
|
||||||
%></header>
|
%></header>
|
||||||
|
|
||||||
<div class='comment-form-container'></div>
|
<form class='body'>
|
||||||
|
<div class='keep-height'>
|
||||||
|
<div class='tab preview'>
|
||||||
|
<div class='comment-content'>
|
||||||
|
<%= ctx.makeMarkdown(ctx.comment ? ctx.comment.text : '') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='tab edit'>
|
||||||
|
<textarea required minlength=1><%- ctx.comment ? ctx.comment.text : '' %></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class='edit'>
|
||||||
|
<div class='messages'></div>
|
||||||
|
|
||||||
|
<input type='submit' class='save-changes' value='Save'/>
|
||||||
|
<% if (!ctx.onlyEditing) { %>
|
||||||
|
<input type='button' class='cancel-editing discourage' value='Cancel'/>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<div class='tabs'>
|
|
||||||
<form>
|
|
||||||
<div class='tabs-wrapper'><%
|
|
||||||
%><div class='tab-wrapper'><%
|
|
||||||
%><div class='preview tab'><%
|
|
||||||
%><div class='comment-content'><%
|
|
||||||
%><%= ctx.makeMarkdown(ctx.comment.text) %><%
|
|
||||||
%></div><%
|
|
||||||
%></div><%
|
|
||||||
|
|
||||||
%><div class='edit tab'><%
|
|
||||||
%><textarea required minlength=1><%- ctx.comment.text %></textarea><%
|
|
||||||
%></div><%
|
|
||||||
%></div><%
|
|
||||||
%></div>
|
|
||||||
|
|
||||||
<nav class='buttons'>
|
|
||||||
<ul>
|
|
||||||
<li class='preview'><a href>Preview</a></li>
|
|
||||||
<li class='edit'><a href>Edit</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<nav class='actions'>
|
|
||||||
<input type='submit' class='save' value='Save'/>
|
|
||||||
<input type='button' class='cancel discourage' value='Cancel'/>
|
|
||||||
</nav>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class='messages'></div>
|
|
||||||
</div>
|
|
|
@ -4,7 +4,6 @@ const router = require('../router.js');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const settings = require('../models/settings.js');
|
const settings = require('../models/settings.js');
|
||||||
const Comment = require('../models/comment.js');
|
|
||||||
const Post = require('../models/post.js');
|
const Post = require('../models/post.js');
|
||||||
const PostList = require('../models/post_list.js');
|
const PostList = require('../models/post_list.js');
|
||||||
const PostDetailView = require('../views/post_detail_view.js');
|
const PostDetailView = require('../views/post_detail_view.js');
|
||||||
|
|
|
@ -69,10 +69,10 @@ class PostMainController extends BasePostController {
|
||||||
'merge', e => this._evtMergePost(e));
|
'merge', e => this._evtMergePost(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._view.commentFormControl) {
|
if (this._view.commentControl) {
|
||||||
this._view.commentFormControl.addEventListener(
|
this._view.commentControl.addEventListener(
|
||||||
'change', e => this._evtCommentChange(e));
|
'change', e => this._evtCommentChange(e));
|
||||||
this._view.commentFormControl.addEventListener(
|
this._view.commentControl.addEventListener(
|
||||||
'submit', e => this._evtCreateComment(e));
|
'submit', e => this._evtCreateComment(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,18 +183,18 @@ class PostMainController extends BasePostController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtCreateComment(e) {
|
_evtCreateComment(e) {
|
||||||
// TODO: disable form
|
this._view.commentControl.disableForm();
|
||||||
const comment = Comment.create(this._post.id);
|
const comment = Comment.create(this._post.id);
|
||||||
comment.text = e.detail.text;
|
comment.text = e.detail.text;
|
||||||
comment.save()
|
comment.save()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._post.comments.add(comment);
|
this._post.comments.add(comment);
|
||||||
this._view.commentFormControl.setText('');
|
this._view.commentControl.exitEditMode();
|
||||||
// TODO: enable form
|
this._view.commentControl.enableForm();
|
||||||
misc.disableExitConfirmation();
|
misc.disableExitConfirmation();
|
||||||
}, errorMessage => {
|
}, errorMessage => {
|
||||||
this._view.commentFormControl.showError(errorMessage);
|
this._view.commentControl.showError(errorMessage);
|
||||||
// TODO: enable form
|
this._view.commentControl.enableForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,87 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
|
const misc = require('../util/misc.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
const CommentFormControl = require('../controls/comment_form_control.js');
|
|
||||||
|
|
||||||
const template = views.getTemplate('comment');
|
const template = views.getTemplate('comment');
|
||||||
const scoreTemplate = views.getTemplate('score');
|
const scoreTemplate = views.getTemplate('score');
|
||||||
|
|
||||||
class CommentControl extends events.EventTarget {
|
class CommentControl extends events.EventTarget {
|
||||||
constructor(hostNode, comment) {
|
constructor(hostNode, comment, onlyEditing) {
|
||||||
super();
|
super();
|
||||||
this._hostNode = hostNode;
|
this._hostNode = hostNode;
|
||||||
this._comment = comment;
|
this._comment = comment;
|
||||||
|
this._onlyEditing = onlyEditing;
|
||||||
|
|
||||||
comment.addEventListener('change', e => this._evtChange(e));
|
if (comment) {
|
||||||
comment.addEventListener('changeScore', e => this._evtChangeScore(e));
|
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';
|
const infix = isLoggedIn ? 'own' : 'any';
|
||||||
views.replaceContent(this._hostNode, template({
|
views.replaceContent(this._hostNode, template({
|
||||||
comment: this._comment,
|
comment: comment,
|
||||||
|
user: comment ? comment.user : api.user,
|
||||||
canViewUsers: api.hasPrivilege('users:view'),
|
canViewUsers: api.hasPrivilege('users:view'),
|
||||||
canEditComment: api.hasPrivilege(`comments:edit:${infix}`),
|
canEditComment: api.hasPrivilege(`comments:edit:${infix}`),
|
||||||
canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`),
|
canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`),
|
||||||
|
onlyEditing: onlyEditing,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (this._editButtonNode) {
|
if (this._editButtonNodes) {
|
||||||
this._editButtonNode.addEventListener(
|
for (let node of this._editButtonNodes) {
|
||||||
'click', e => this._evtEditClick(e));
|
node.addEventListener('click', e => this._evtEditClick(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this._deleteButtonNode) {
|
if (this._deleteButtonNode) {
|
||||||
this._deleteButtonNode.addEventListener(
|
this._deleteButtonNode.addEventListener(
|
||||||
'click', e => this._evtDeleteClick(e));
|
'click', e => this._evtDeleteClick(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._formControl = new CommentFormControl(
|
if (this._previewEditingButtonNode) {
|
||||||
this._hostNode.querySelector('.comment-form-container'),
|
this._previewEditingButtonNode.addEventListener(
|
||||||
this._comment,
|
'click', e => this._evtPreviewEditingClick(e));
|
||||||
true);
|
}
|
||||||
events.proxyEvent(this._formControl, this, 'submit');
|
|
||||||
|
if (this._saveChangesButtonNode) {
|
||||||
|
this._saveChangesButtonNode.addEventListener(
|
||||||
|
'click', e => this._evtSaveChangesClick(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._cancelEditingButtonNode) {
|
||||||
|
this._cancelEditingButtonNode.addEventListener(
|
||||||
|
'click', e => this._evtCancelEditingClick(e));
|
||||||
|
}
|
||||||
|
|
||||||
this._installScore();
|
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() {
|
get _scoreContainerNode() {
|
||||||
return this._hostNode.querySelector('.score-container');
|
return this._hostNode.querySelector('.score-container');
|
||||||
}
|
}
|
||||||
|
|
||||||
get _editButtonNode() {
|
get _editButtonNodes() {
|
||||||
return this._hostNode.querySelector('.edit');
|
return this._hostNode.querySelectorAll('li.edit>a, a.edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _previewEditingButtonNode() {
|
||||||
|
return this._hostNode.querySelector('li.preview>a');
|
||||||
}
|
}
|
||||||
|
|
||||||
get _deleteButtonNode() {
|
get _deleteButtonNode() {
|
||||||
|
@ -64,12 +96,32 @@ class CommentControl extends events.EventTarget {
|
||||||
return this._hostNode.querySelector('.downvote');
|
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() {
|
_installScore() {
|
||||||
views.replaceContent(
|
views.replaceContent(
|
||||||
this._scoreContainerNode,
|
this._scoreContainerNode,
|
||||||
scoreTemplate({
|
scoreTemplate({
|
||||||
score: this._comment.score,
|
score: this._comment ? this._comment.score : 0,
|
||||||
ownScore: this._comment.ownScore,
|
ownScore: this._comment ? this._comment.ownScore : 0,
|
||||||
canScore: api.hasPrivilege('comments:score'),
|
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) {
|
_evtEditClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._formControl.enterEditMode();
|
this.enterEditMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtScoreClick(e, score) {
|
_evtScoreClick(e, score) {
|
||||||
|
@ -114,12 +197,69 @@ class CommentControl extends events.EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtChange(e) {
|
_evtChange(e) {
|
||||||
this._formControl.exitEditMode();
|
this.exitEditMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
_evtChangeScore(e) {
|
_evtChangeScore(e) {
|
||||||
this._installScore();
|
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;
|
module.exports = CommentControl;
|
||||||
|
|
|
@ -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;
|
|
|
@ -34,7 +34,7 @@ class CommentListControl extends events.EventTarget {
|
||||||
_installCommentNode(comment) {
|
_installCommentNode(comment) {
|
||||||
const commentListItemNode = document.createElement('li');
|
const commentListItemNode = document.createElement('li');
|
||||||
const commentControl = new CommentControl(
|
const commentControl = new CommentControl(
|
||||||
commentListItemNode, comment);
|
commentListItemNode, comment, false);
|
||||||
events.proxyEvent(commentControl, this, 'submit');
|
events.proxyEvent(commentControl, this, 'submit');
|
||||||
events.proxyEvent(commentControl, this, 'score');
|
events.proxyEvent(commentControl, this, 'score');
|
||||||
events.proxyEvent(commentControl, this, 'delete');
|
events.proxyEvent(commentControl, this, 'delete');
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Comment extends events.EventTarget {
|
||||||
|
|
||||||
get id() { return this._id; }
|
get id() { return this._id; }
|
||||||
get postId() { return this._postId; }
|
get postId() { return this._postId; }
|
||||||
get text() { return this._text; }
|
get text() { return this._text || ''; }
|
||||||
get user() { return this._user; }
|
get user() { return this._user; }
|
||||||
get creationTime() { return this._creationTime; }
|
get creationTime() { return this._creationTime; }
|
||||||
get lastEditTime() { return this._lastEditTime; }
|
get lastEditTime() { return this._lastEditTime; }
|
||||||
|
|
|
@ -10,8 +10,8 @@ const PostReadonlySidebarControl =
|
||||||
require('../controls/post_readonly_sidebar_control.js');
|
require('../controls/post_readonly_sidebar_control.js');
|
||||||
const PostEditSidebarControl =
|
const PostEditSidebarControl =
|
||||||
require('../controls/post_edit_sidebar_control.js');
|
require('../controls/post_edit_sidebar_control.js');
|
||||||
|
const CommentControl = require('../controls/comment_control.js');
|
||||||
const CommentListControl = require('../controls/comment_list_control.js');
|
const CommentListControl = require('../controls/comment_list_control.js');
|
||||||
const CommentFormControl = require('../controls/comment_form_control.js');
|
|
||||||
|
|
||||||
const template = views.getTemplate('post-main');
|
const template = views.getTemplate('post-main');
|
||||||
|
|
||||||
|
@ -101,9 +101,8 @@ class PostMainView {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.commentFormControl = new CommentFormControl(
|
this.commentControl = new CommentControl(
|
||||||
commentFormContainer, null, false, 150);
|
commentFormContainer, null, true);
|
||||||
this.commentFormControl.enterEditMode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_installComments(comments) {
|
_installComments(comments) {
|
||||||
|
|
Loading…
Reference in New Issue