client/comments: add comment list view for post
This commit is contained in:
parent
0908323290
commit
7e8a9a0948
18 changed files with 581 additions and 59 deletions
|
@ -1,4 +1,5 @@
|
|||
$main-color = #24AADD
|
||||
$window-color = white
|
||||
$top-nav-color = #F5F5F5
|
||||
$text-color = #111
|
||||
$inactive-link-color = #888
|
||||
|
|
136
client/css/comments.styl
Normal file
136
client/css/comments.styl
Normal file
|
@ -0,0 +1,136 @@
|
|||
@import colors
|
||||
|
||||
.comments>ul
|
||||
list-style-type: none
|
||||
margin: 1em 0
|
||||
padding: 0
|
||||
|
||||
.comment
|
||||
margin: 0 0 1em 0
|
||||
padding: 0
|
||||
display: -webkit-flex
|
||||
display: flex
|
||||
|
||||
&:not(.editing)
|
||||
.tabs nav
|
||||
display: none
|
||||
.tabs .edit.tab
|
||||
display: none
|
||||
&.editing
|
||||
.tab:not(.active)
|
||||
display: none
|
||||
.tabs-wrapper
|
||||
background: $active-tab-background-color
|
||||
.tab
|
||||
padding: 1em
|
||||
.content-wrapper
|
||||
background: $window-color
|
||||
overflow: hidden
|
||||
.content
|
||||
margin: 1em
|
||||
textarea
|
||||
resize: vertical
|
||||
width: 100%
|
||||
max-height: 80vh
|
||||
box-sizing: padding-box
|
||||
|
||||
.avatar
|
||||
margin-right: 1em
|
||||
-webkit-flex-shrink: 0
|
||||
flex-shrink: 0
|
||||
vertical-align: top
|
||||
|
||||
.thumbnail
|
||||
width: 40px
|
||||
height: 40px
|
||||
margin: 0
|
||||
|
||||
.body
|
||||
width: 100%
|
||||
|
||||
header
|
||||
line-height: 16pt
|
||||
vertical-align: middle
|
||||
margin-bottom: 0.5em
|
||||
background: $top-nav-color
|
||||
padding: 0.2em 0.5em
|
||||
|
||||
.date, .score-container, .edit, .delete
|
||||
margin-left: 2em
|
||||
font-size: 95%
|
||||
.edit, .delete, .score-container a, .nickname a
|
||||
color: mix($main-color, $inactive-tab-text-color)
|
||||
.edit, .delete
|
||||
font-size: 80%
|
||||
|
||||
i
|
||||
margin-right: 0.3em
|
||||
.downvote i
|
||||
text-align: right
|
||||
.upvote i
|
||||
display: inline-block
|
||||
width: 1em
|
||||
margin: 0
|
||||
.value
|
||||
text-align: center
|
||||
display: inline-block
|
||||
width: 2em
|
||||
|
||||
form
|
||||
width: auto
|
||||
margin: 0
|
||||
|
||||
nav
|
||||
vertical-align: middle
|
||||
margin: 0 0.8em 0.5em 0
|
||||
&.buttons
|
||||
float: left
|
||||
&.actions
|
||||
float: left
|
||||
margin-top: 0.3em
|
||||
|
||||
.messages
|
||||
margin: 1em 0
|
||||
|
||||
.content
|
||||
ul
|
||||
list-style-position: inside
|
||||
margin: 1em 0
|
||||
padding: 0
|
||||
|
||||
.sjis
|
||||
font-family: 'MS PGothic', 'MS Pゴシック', 'IPAMonaPGothic', 'Trebuchet MS', Verdana, Futura, Arial, Helvetica, sans-serif
|
||||
background: #fbfbfb
|
||||
color: #111
|
||||
font-size: 12pt
|
||||
line-height: 1
|
||||
margin: 0
|
||||
padding: 4px
|
||||
overflow: auto
|
||||
white-space: pre
|
||||
word-wrap: normal
|
||||
|
||||
p:first-child
|
||||
margin-top: 0
|
||||
|
||||
.spoiler
|
||||
background: #eee
|
||||
color: #eee
|
||||
&:hover
|
||||
color: dimgray
|
||||
&:before
|
||||
content: '['
|
||||
color: #000
|
||||
&:after
|
||||
content: ']'
|
||||
color: #000
|
||||
|
||||
blockquote
|
||||
border-left: 3px solid #eee
|
||||
margin-left: 0
|
||||
padding: 0.3em 0.3em 0.3em 0.7em
|
||||
background: #fafafa
|
||||
color: #444
|
||||
|
||||
blockquote :last-child
|
||||
margin-bottom: 0
|
|
@ -270,6 +270,11 @@ input[type=submit]
|
|||
background-color: $button-disabled-background-color
|
||||
color: $button-disabled-text-color
|
||||
|
||||
&.discourage
|
||||
border-color: transparent
|
||||
background-color: transparent
|
||||
color: $button-disabled-text-color
|
||||
|
||||
&:focus
|
||||
border: 2px solid $text-color
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ body
|
|||
min-height: 100%
|
||||
|
||||
body
|
||||
background: $window-color
|
||||
overflow-y: scroll
|
||||
margin: 0
|
||||
color: $text-color
|
||||
|
|
|
@ -65,7 +65,7 @@ $safety-unsafe = #F3985F
|
|||
|
||||
.social
|
||||
margin-top: 1em
|
||||
.score
|
||||
.score-container
|
||||
float: left
|
||||
margin-right: 3em
|
||||
.downvote i
|
||||
|
|
75
client/html/comment.tpl
Normal file
75
client/html/comment.tpl
Normal file
|
@ -0,0 +1,75 @@
|
|||
<div class='comment'>
|
||||
<div class='avatar'>
|
||||
<% if (ctx.comment.user.name && ctx.canViewUsers) { %>
|
||||
<a href='/user/<%= ctx.comment.user.name %>'>
|
||||
<% } %>
|
||||
|
||||
<%= ctx.makeThumbnail(ctx.comment.user.avatarUrl) %>
|
||||
|
||||
<% if (ctx.comment.user.name && ctx.canViewUsers) { %>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class='body'>
|
||||
<header><!--
|
||||
--><span class='nickname'><!--
|
||||
--><% if (ctx.comment.user.name && ctx.canViewUsers) { %><!--
|
||||
--><a href='/user/<%= ctx.comment.user.name %>'><!--
|
||||
--><% } %><!--
|
||||
|
||||
--><%= ctx.comment.user.name %><!--
|
||||
|
||||
--><% if (ctx.comment.user.name && ctx.canViewUsers) { %><!--
|
||||
--></a><!--
|
||||
--><% } %><!--
|
||||
--></span><!--
|
||||
|
||||
--><span class='date'><!--
|
||||
--><%= ctx.makeRelativeTime(ctx.comment.creationTime) %><!--
|
||||
--></span><!--
|
||||
|
||||
--><span class='score-container'></span><!--
|
||||
|
||||
--><% if (ctx.canEditComment) { %><!--
|
||||
--><a class='edit' href='#'><!--
|
||||
--><i class='fa fa-pencil'></i> edit<!--
|
||||
--></a><!--
|
||||
--><% } %><!--
|
||||
|
||||
--><% if (ctx.canDeleteComment) { %><!--
|
||||
--><a class='delete' href='#'><!--
|
||||
--><i class='fa fa-remove'></i> delete<!--
|
||||
--></a><!--
|
||||
--><% } %><!--
|
||||
--></header>
|
||||
|
||||
<div class='tabs'>
|
||||
<form>
|
||||
<div class='tabs-wrapper'>
|
||||
<div class='preview tab'>
|
||||
<div class='content-wrapper'><div class='content'><%= ctx.makeMarkdown(ctx.comment.text) %></div></div>
|
||||
</div>
|
||||
|
||||
<div class='edit tab'>
|
||||
<textarea required minlength=1><%= ctx.comment.text %></textarea>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
6
client/html/comment_list.tpl
Normal file
6
client/html/comment_list.tpl
Normal file
|
@ -0,0 +1,6 @@
|
|||
<div class='comments'>
|
||||
<% if (ctx.canListComments && ctx.comments.length) { %>
|
||||
<ul>
|
||||
</ul>
|
||||
<% } %>
|
||||
</div>
|
15
client/html/fav.tpl
Normal file
15
client/html/fav.tpl
Normal file
|
@ -0,0 +1,15 @@
|
|||
<% if (ctx.canFavorite) { %>
|
||||
<% if (ctx.ownFavorite) { %>
|
||||
<a class='remove-favorite' href='#'>
|
||||
<i class='fa fa-heart'></i>
|
||||
<% } else { %>
|
||||
<a class='add-favorite' href='#'>
|
||||
<i class='fa fa-heart-o'></i>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<a class='add-favorite inactive'>
|
||||
<i class='fa fa-heart-o'></i>
|
||||
<% } %>
|
||||
<span class='vim-nav-hint'>add to favorites</span>
|
||||
</a>
|
||||
<span class='value'><%= ctx.favoriteCount %></span>
|
|
@ -46,8 +46,6 @@
|
|||
<div class='content'>
|
||||
<div class='post-container'></div>
|
||||
|
||||
<section class='comments'>
|
||||
<!-- TODO: comments -->
|
||||
</section>
|
||||
<div class='comments-container'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -38,53 +38,9 @@
|
|||
</section>
|
||||
|
||||
<section class='social'>
|
||||
<div class='score'>
|
||||
<% if (ctx.canScorePosts) { %>
|
||||
<a class='upvote' href='#'>
|
||||
<% if (ctx.post.ownScore == 1) { %>
|
||||
<i class='fa fa-thumbs-up'></i>
|
||||
<% } else { %>
|
||||
<i class='fa fa-thumbs-o-up'></i>
|
||||
<% } %>
|
||||
<span class='vim-nav-hint'>upvote</span>
|
||||
<span class='vim-nav-hint'>like</span>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<a class='upvote inactive'>
|
||||
<i class='fa fa-thumbs-o-up'></i>
|
||||
</a>
|
||||
<% } %>
|
||||
<span class='value'><%= ctx.post.score %></span>
|
||||
<% if (ctx.canScorePosts) { %>
|
||||
<a class='downvote' href='#'>
|
||||
<% if (ctx.post.ownScore == -1) { %>
|
||||
<i class='fa fa-thumbs-down'></i>
|
||||
<% } else { %>
|
||||
<i class='fa fa-thumbs-o-down'></i>
|
||||
<% } %>
|
||||
<span class='vim-nav-hint'>downvote</span>
|
||||
<span class='vim-nav-hint'>dislike</span>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class='score-container'></div>
|
||||
|
||||
<div class='fav'>
|
||||
<% if (ctx.canFavoritePosts) { %>
|
||||
<% if (ctx.post.ownFavorite) { %>
|
||||
<a class='remove-favorite' href='#'>
|
||||
<i class='fa fa-heart'></i>
|
||||
<% } else { %>
|
||||
<a class='add-favorite' href='#'>
|
||||
<i class='fa fa-heart-o'></i>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<a class='add-favorite inactive'>
|
||||
<i class='fa fa-heart-o'></i>
|
||||
<% } %>
|
||||
<span class='vim-nav-hint'>add to favorites</span>
|
||||
</a>
|
||||
<span class='value'><%= ctx.post.favoriteCount %></span>
|
||||
</div>
|
||||
<div class='fav-container'></div>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
|
|
27
client/html/score.tpl
Normal file
27
client/html/score.tpl
Normal file
|
@ -0,0 +1,27 @@
|
|||
<% if (ctx.canScore) { %>
|
||||
<a class='upvote' href='#'>
|
||||
<% if (ctx.ownScore == 1) { %>
|
||||
<i class='fa fa-thumbs-up'></i>
|
||||
<% } else { %>
|
||||
<i class='fa fa-thumbs-o-up'></i>
|
||||
<% } %>
|
||||
<span class='vim-nav-hint'>upvote</span>
|
||||
<span class='vim-nav-hint'>like</span>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<a class='upvote inactive'>
|
||||
<i class='fa fa-thumbs-o-up'></i>
|
||||
</a>
|
||||
<% } %>
|
||||
<span class='value'><%= ctx.score %></span>
|
||||
<% if (ctx.canScore) { %>
|
||||
<a class='downvote' href='#'>
|
||||
<% if (ctx.ownScore == -1) { %>
|
||||
<i class='fa fa-thumbs-down'></i>
|
||||
<% } else { %>
|
||||
<i class='fa fa-thumbs-o-down'></i>
|
||||
<% } %>
|
||||
<span class='vim-nav-hint'>downvote</span>
|
||||
<span class='vim-nav-hint'>dislike</span>
|
||||
</a>
|
||||
<% } %>
|
185
client/js/controls/comment_control.js
Normal file
185
client/js/controls/comment_control.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
|
||||
class CommentControl {
|
||||
constructor(hostNode, comment) {
|
||||
this._hostNode = hostNode;
|
||||
this._comment = comment;
|
||||
this._template = views.getTemplate('comment');
|
||||
this._scoreTemplate = views.getTemplate('score');
|
||||
|
||||
this.install();
|
||||
}
|
||||
|
||||
install() {
|
||||
const isLoggedIn = api.isLoggedIn(this._comment.user);
|
||||
const infix = isLoggedIn ? 'own' : 'any';
|
||||
const sourceNode = this._template({
|
||||
comment: this._comment,
|
||||
canViewUsers: api.hasPrivilege('users:view'),
|
||||
canEditComment: api.hasPrivilege(`comments:edit:${infix}`),
|
||||
canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`),
|
||||
});
|
||||
|
||||
views.showView(
|
||||
sourceNode.querySelector('.score-container'),
|
||||
this._scoreTemplate({
|
||||
score: this._comment.score,
|
||||
ownScore: this._comment.ownScore,
|
||||
canScore: api.hasPrivilege('comments:score'),
|
||||
}));
|
||||
|
||||
const editButton = sourceNode.querySelector('.edit');
|
||||
const deleteButton = sourceNode.querySelector('.delete');
|
||||
const upvoteButton = sourceNode.querySelector('.upvote');
|
||||
const downvoteButton = sourceNode.querySelector('.downvote');
|
||||
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');
|
||||
|
||||
if (editButton) {
|
||||
editButton.addEventListener(
|
||||
'click', e => this._evtEditClick(e));
|
||||
}
|
||||
if (deleteButton) {
|
||||
deleteButton.addEventListener(
|
||||
'click', e => this._evtDeleteClick(e));
|
||||
}
|
||||
|
||||
if (upvoteButton) {
|
||||
upvoteButton.addEventListener(
|
||||
'click',
|
||||
e => this._evtScoreClick(
|
||||
e, () => this._comment.ownScore === 1 ? 0 : 1));
|
||||
}
|
||||
if (downvoteButton) {
|
||||
downvoteButton.addEventListener(
|
||||
'click',
|
||||
e => this._evtScoreClick(
|
||||
e, () => this._comment.ownScore === -1 ? 0 : -1));
|
||||
}
|
||||
|
||||
previewTabButton.addEventListener(
|
||||
'click', e => this._evtPreviewClick(e));
|
||||
editTabButton.addEventListener(
|
||||
'click', e => this._evtEditClick(e));
|
||||
|
||||
formNode.addEventListener('submit', e => this._evtSaveClick(e));
|
||||
cancelButton.addEventListener('click', e => this._evtCancelClick(e));
|
||||
|
||||
for (let event of ['cut', 'paste', 'drop', 'keydown']) {
|
||||
textareaNode.addEventListener(event, e => {
|
||||
window.setTimeout(() => this._growTextArea(), 0);
|
||||
});
|
||||
}
|
||||
textareaNode.addEventListener('change', e => { this._growTextArea(); });
|
||||
|
||||
views.showView(this._hostNode, sourceNode);
|
||||
}
|
||||
|
||||
_evtScoreClick(e, scoreGetter) {
|
||||
e.preventDefault();
|
||||
api.put(
|
||||
'/comment/' + this._comment.id + '/score',
|
||||
{score: scoreGetter()})
|
||||
.then(
|
||||
response => {
|
||||
this._comment.score = parseInt(response.score);
|
||||
this._comment.ownScore = parseInt(response.ownScore);
|
||||
this.install();
|
||||
}, response => {
|
||||
window.alert(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_evtDeleteClick(e) {
|
||||
e.preventDefault();
|
||||
if (!window.confirm('Are you sure you want to delete this comment?')) {
|
||||
return;
|
||||
}
|
||||
api.delete('/comment/' + this._comment.id)
|
||||
.then(response => {
|
||||
this._hostNode.parentNode.removeChild(this._hostNode);
|
||||
}, response => {
|
||||
window.alert(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_evtSaveClick(e) {
|
||||
e.preventDefault();
|
||||
api.put('/comment/' + this._comment.id, {
|
||||
text: this._hostNode.querySelector('.edit.tab textarea').value,
|
||||
}).then(response => {
|
||||
this._comment = response;
|
||||
this.install();
|
||||
}, response => {
|
||||
this._showError(response.description);
|
||||
});
|
||||
}
|
||||
|
||||
_evtPreviewClick(e) {
|
||||
e.preventDefault();
|
||||
this._hostNode.querySelector('.preview.tab .content').innerHTML
|
||||
= misc.formatMarkdown(
|
||||
this._hostNode.querySelector('.edit.tab textarea').value);
|
||||
this._freezeTabHeights();
|
||||
this._selectTab('preview');
|
||||
}
|
||||
|
||||
_evtEditClick(e) {
|
||||
e.preventDefault();
|
||||
this._freezeTabHeights();
|
||||
this._enterEditMode();
|
||||
this._selectTab('edit');
|
||||
this._growTextArea();
|
||||
}
|
||||
|
||||
_evtCancelClick(e) {
|
||||
e.preventDefault();
|
||||
this._exitEditMode();
|
||||
this._hostNode.querySelector('.edit.tab textarea').value
|
||||
= this._comment.text;
|
||||
}
|
||||
|
||||
_enterEditMode() {
|
||||
this._hostNode.querySelector('.comment').classList.add('editing');
|
||||
misc.enableExitConfirmation();
|
||||
}
|
||||
|
||||
_exitEditMode() {
|
||||
this._hostNode.querySelector('.comment').classList.remove('editing');
|
||||
this._hostNode.querySelector('.tabs-wrapper').style.minHeight = null;
|
||||
misc.disableExitConfirmation();
|
||||
views.clearMessages(this._hostNode);
|
||||
}
|
||||
|
||||
_selectTab(tabName) {
|
||||
this._freezeTabHeights();
|
||||
for (let tab of this._hostNode.querySelectorAll('.tab, .buttons li')) {
|
||||
tab.classList.toggle('active', tab.classList.contains(tabName));
|
||||
}
|
||||
}
|
||||
|
||||
_freezeTabHeights() {
|
||||
const tabsNode = this._hostNode.querySelector('.tabs-wrapper');
|
||||
const tabsHeight = tabsNode.getBoundingClientRect().height;
|
||||
tabsNode.style.minHeight = tabsHeight + 'px';
|
||||
}
|
||||
|
||||
_growTextArea() {
|
||||
const previewNode = this._hostNode.querySelector('.content');
|
||||
const textareaNode = this._hostNode.querySelector('textarea');
|
||||
textareaNode.style.height = textareaNode.scrollHeight + 'px';
|
||||
}
|
||||
|
||||
_showError(message) {
|
||||
views.showError(this._hostNode, message);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = CommentControl;
|
41
client/js/controls/comment_list_control.js
Normal file
41
client/js/controls/comment_list_control.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
const api = require('../api.js');
|
||||
const views = require('../util/views.js');
|
||||
const CommentControl = require('../controls/comment_control.js');
|
||||
|
||||
class CommentListControl {
|
||||
constructor(hostNode, comments) {
|
||||
this._hostNode = hostNode;
|
||||
this._comments = comments;
|
||||
this._template = views.getTemplate('comment-list');
|
||||
|
||||
this.install();
|
||||
}
|
||||
|
||||
install() {
|
||||
const sourceNode = this._template({
|
||||
comments: this._comments,
|
||||
canListComments: api.hasPrivilege('comments:list'),
|
||||
});
|
||||
|
||||
views.showView(this._hostNode, sourceNode);
|
||||
|
||||
this._renderComments();
|
||||
}
|
||||
|
||||
_renderComments() {
|
||||
if (!this._comments.length) {
|
||||
return;
|
||||
}
|
||||
const commentList = new DocumentFragment();
|
||||
for (let comment of this._comments) {
|
||||
const commentListItemNode = document.createElement('li');
|
||||
new CommentControl(commentListItemNode, comment);
|
||||
commentList.appendChild(commentListItemNode);
|
||||
}
|
||||
views.showView(this._hostNode.querySelector('ul'), commentList);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = CommentListControl;
|
|
@ -10,6 +10,8 @@ class PostReadonlySidebarControl {
|
|||
this._post = post;
|
||||
this._postContentControl = postContentControl;
|
||||
this._template = views.getTemplate('post-readonly-sidebar');
|
||||
this._scoreTemplate = views.getTemplate('score');
|
||||
this._favTemplate = views.getTemplate('fav');
|
||||
|
||||
this.install();
|
||||
}
|
||||
|
@ -20,10 +22,25 @@ class PostReadonlySidebarControl {
|
|||
getTagCategory: this._getTagCategory,
|
||||
getTagUsages: this._getTagUsages,
|
||||
canListPosts: api.hasPrivilege('posts:list'),
|
||||
canScorePosts: api.hasPrivilege('posts:score'),
|
||||
canFavoritePosts: api.hasPrivilege('posts:favorite'),
|
||||
canViewTags: api.hasPrivilege('tags:view'),
|
||||
});
|
||||
|
||||
views.showView(
|
||||
sourceNode.querySelector('.score-container'),
|
||||
this._scoreTemplate({
|
||||
score: this._post.score,
|
||||
ownScore: this._post.ownScore,
|
||||
canScore: api.hasPrivilege('posts:score'),
|
||||
}));
|
||||
|
||||
views.showView(
|
||||
sourceNode.querySelector('.fav-container'),
|
||||
this._favTemplate({
|
||||
favoriteCount: this._post.favoriteCount,
|
||||
ownFavorite: this._post.ownFavorite,
|
||||
canFavorite: api.hasPrivilege('posts:favorite'),
|
||||
}));
|
||||
|
||||
const upvoteButton = sourceNode.querySelector('.upvote');
|
||||
const downvoteButton = sourceNode.querySelector('.downvote')
|
||||
const addFavButton = sourceNode.querySelector('.add-favorite')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
require('./util/polyfill.js');
|
||||
const misc = require('./util/misc.js');
|
||||
|
||||
const page = require('page');
|
||||
const origPushState = page.Context.prototype.pushState;
|
||||
|
@ -9,6 +10,20 @@ page.Context.prototype.pushState = function() {
|
|||
origPushState.call(this);
|
||||
};
|
||||
|
||||
page.cancel = function(ctx) {
|
||||
prevContext = ctx;
|
||||
ctx.pushState();
|
||||
};
|
||||
|
||||
page.exit((ctx, next) => {
|
||||
views.unlistenToMessages();
|
||||
if (misc.confirmPageExit()) {
|
||||
next();
|
||||
} else {
|
||||
page.cancel(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
const mousetrap = require('mousetrap');
|
||||
page(/.*/, (ctx, next) => {
|
||||
mousetrap.reset();
|
||||
|
@ -34,11 +49,6 @@ for (let controller of controllers) {
|
|||
controller.registerRoutes();
|
||||
}
|
||||
|
||||
page.exit((ctx, next) => {
|
||||
views.unlistenToMessages();
|
||||
next();
|
||||
});
|
||||
|
||||
const api = require('./api.js');
|
||||
Promise.all([tags.refreshExport(), api.loginFromCookies()])
|
||||
.then(() => {
|
||||
|
|
|
@ -199,6 +199,27 @@ function unindent(callSite, ...args) {
|
|||
return format(output);
|
||||
}
|
||||
|
||||
function enableExitConfirmation() {
|
||||
window.onbeforeunload = e => {
|
||||
return 'Are you sure you want to leave? ' +
|
||||
'Data you have entered may not be saved.';
|
||||
};
|
||||
}
|
||||
|
||||
function disableExitConfirmation() {
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
|
||||
function confirmPageExit() {
|
||||
if (!window.onbeforeunload) {
|
||||
return true;
|
||||
}
|
||||
if (window.confirm(window.onbeforeunload())) {
|
||||
disableExitConfirmation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
range: range,
|
||||
formatSearchQuery: formatSearchQuery,
|
||||
|
@ -208,4 +229,7 @@ module.exports = {
|
|||
formatFileSize: formatFileSize,
|
||||
formatMarkdown: formatMarkdown,
|
||||
unindent: unindent,
|
||||
enableExitConfirmation: enableExitConfirmation,
|
||||
disableExitConfirmation: disableExitConfirmation,
|
||||
confirmPageExit: confirmPageExit,
|
||||
};
|
||||
|
|
|
@ -29,6 +29,10 @@ function makeFileSize(fileSize) {
|
|||
return misc.formatFileSize(fileSize);
|
||||
}
|
||||
|
||||
function makeMarkdown(text) {
|
||||
return misc.formatMarkdown(text);
|
||||
}
|
||||
|
||||
function makeRelativeTime(time) {
|
||||
return makeNonVoidElement(
|
||||
'time',
|
||||
|
@ -202,7 +206,7 @@ function makeVoidElement(name, attributes) {
|
|||
return `<${_serializeElement(name, attributes)}/>`;
|
||||
}
|
||||
|
||||
function _messageHandler(target, message, className) {
|
||||
function showMessage(target, message, className) {
|
||||
if (!message) {
|
||||
message = 'Unknown message';
|
||||
}
|
||||
|
@ -222,6 +226,18 @@ function _messageHandler(target, message, className) {
|
|||
return true;
|
||||
}
|
||||
|
||||
function showError(target, message) {
|
||||
return showMessage(target, message, 'error');
|
||||
}
|
||||
|
||||
function showSuccess(target, message) {
|
||||
return showMessage(target, message, 'success');
|
||||
}
|
||||
|
||||
function showInfo(target, message) {
|
||||
return showMessage(target, message, 'info');
|
||||
}
|
||||
|
||||
function unlistenToMessages() {
|
||||
events.unlisten(events.Success);
|
||||
events.unlisten(events.Error);
|
||||
|
@ -234,7 +250,7 @@ function listenToMessages(target) {
|
|||
events.listen(
|
||||
eventType,
|
||||
msg => {
|
||||
return _messageHandler(target, msg, className);
|
||||
return showMessage(target, msg, className);
|
||||
});
|
||||
};
|
||||
listen(events.Success, 'success');
|
||||
|
@ -269,6 +285,7 @@ function getTemplate(templatePath) {
|
|||
Object.assign(ctx, {
|
||||
makeRelativeTime: makeRelativeTime,
|
||||
makeFileSize: makeFileSize,
|
||||
makeMarkdown: makeMarkdown,
|
||||
makeThumbnail: makeThumbnail,
|
||||
makeRadio: makeRadio,
|
||||
makeCheckbox: makeCheckbox,
|
||||
|
@ -420,4 +437,7 @@ module.exports = {
|
|||
slideDown: slideDown,
|
||||
slideUp: slideUp,
|
||||
monitorNodeRemoval: monitorNodeRemoval,
|
||||
showError: showError,
|
||||
showSuccess: showSuccess,
|
||||
showInfo: showInfo,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ const PostReadonlySidebarControl
|
|||
= require('../controls/post_readonly_sidebar_control.js');
|
||||
const PostEditSidebarControl
|
||||
= require('../controls/post_edit_sidebar_control.js');
|
||||
const CommentListControl = require('../controls/comment_list_control.js');
|
||||
|
||||
class PostView {
|
||||
constructor() {
|
||||
|
@ -63,6 +64,10 @@ class PostView {
|
|||
this._postContentControl);
|
||||
}
|
||||
|
||||
new CommentListControl(
|
||||
postViewNode.querySelector('.comments-container'),
|
||||
ctx.post.comments);
|
||||
|
||||
keyboard.bind('e', () => {
|
||||
if (ctx.editMode) {
|
||||
page.show('/post/' + ctx.post.id);
|
||||
|
|
Loading…
Reference in a new issue