Added prev/next post controls

This commit is contained in:
Marcin Kurczewski 2014-10-05 10:09:02 +02:00
parent 33c1d99583
commit 688b5b1281
12 changed files with 200 additions and 9 deletions

4
TODO
View file

@ -6,10 +6,6 @@ everything related to posts:
- better thumbnail loading
- single post view
- previous and next post (difficult)
- remember last search
- take care of pages
- add A/D hotkeys
- editing
- ability to loop video posts

View file

@ -9,6 +9,25 @@
border: 0;
}
#post-current-search-wrapper {
text-align: center;
}
#post-current-search {
margin: 0 auto 1em auto;
display: inline-block;
}
#post-current-search a {
margin: 0 2em;
}
#post-current-search a,
#post-current-search div {
display: inline-block;
}
#post-current-search a:not(.enabled) {
color: silver;
}
#post-view-wrapper #sidebar {
line-height: 1.33em;
font-size: 90%;

View file

@ -89,6 +89,8 @@
<script type="text/javascript" src="/js/Controls/TagInput.js"></script>
<script type="text/javascript" src="/js/PresenterManager.js"></script>
<script type="text/javascript" src="/js/Services/PostsAroundCalculator.js"></script>
<script type="text/javascript" src="/js/Presenters/TopNavigationPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/PagerPresenter.js"></script>
<script type="text/javascript" src="/js/Presenters/MessagePresenter.js"></script>

View file

@ -22,10 +22,15 @@ App.Keyboard = function(mousetrap) {
mousetrap.reset();
}
function unbind(key) {
mousetrap.unbind(key);
}
return {
keydown: keydown,
keyup: keyup,
reset: reset,
unbind: unbind,
};
};

View file

@ -9,6 +9,7 @@ App.Pager = function(
var pageNumber;
var searchParams;
var url;
var cache = {};
function init(args) {
url = args.url;
@ -55,7 +56,7 @@ App.Pager = function(
function retrieve() {
return promise.make(function(resolve, reject) {
promise.wait(api.get(url, _.extend({}, searchParams, {page: pageNumber})))
promise.wait(api.get(url, _.extend({page: pageNumber}, searchParams)))
.then(function(response) {
var pageSize = response.json.pageSize;
var totalRecords = response.json.totalRecords;
@ -71,6 +72,27 @@ App.Pager = function(
});
}
function retrieveCached() {
return promise.make(function(resolve, reject) {
var cacheKey = JSON.stringify(_.extend({}, searchParams, {url: url, page: getPage()}));
if (cacheKey in cache) {
resolve.apply(this, cache[cacheKey]);
} else {
promise.wait(retrieve())
.then(function() {
cache[cacheKey] = arguments;
resolve.apply(this, arguments);
}).fail(function() {
reject.apply(this, arguments);
});
}
});
}
function resetCache() {
cache = {};
}
function getVisiblePages() {
var pages = [1, totalPages || 1];
var pagesAroundCurrent = 2;
@ -100,7 +122,9 @@ App.Pager = function(
getSearchParams: getSearchParams,
setSearchParams: setSearchParams,
retrieve: retrieve,
retrieveCached: retrieveCached,
getVisiblePages: getVisiblePages,
resetCache: resetCache,
};
};

View file

@ -24,6 +24,7 @@ App.Presenters.PostListPresenter = function(
topNavigationPresenter.select('posts');
topNavigationPresenter.changeTitle('Posts');
searchArgs = util.parseComplexRouteArgs(args.searchArgs);
searchArgs.page = parseInt(searchArgs.page) || 1;
promise.wait(
util.promiseTemplate('post-list'),
@ -93,6 +94,7 @@ App.Presenters.PostListPresenter = function(
_.each(posts, function(post) {
$target.append(jQuery('<li>' + itemTemplate({
searchArgs: searchArgs,
post: post,
}) + '</li>'));
});

View file

@ -11,6 +11,7 @@ App.Presenters.PostPresenter = function(
router,
keyboard,
presenterManager,
postsAroundCalculator,
postCommentListPresenter,
topNavigationPresenter,
messagePresenter) {
@ -23,11 +24,13 @@ App.Presenters.PostPresenter = function(
var postContentTemplate;
var historyTemplate;
var postNameOrId;
var searchArgs;
var post;
var postScore;
var postFavorites;
var postHistory;
var postNameOrId;
var privileges = {};
var editPrivileges = {};
@ -40,6 +43,7 @@ App.Presenters.PostPresenter = function(
function init(args, loaded) {
topNavigationPresenter.select('posts');
postsAroundCalculator.resetCache();
privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts);
privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts);
@ -76,6 +80,9 @@ App.Presenters.PostPresenter = function(
function reinit(args, loaded) {
postNameOrId = args.postNameOrId;
searchArgs = util.parseComplexRouteArgs(args.searchArgs);
searchArgs.page = parseInt(searchArgs.page) || 1;
promise.wait(refreshPost())
.then(function() {
topNavigationPresenter.changeTitle('@' + post.id);
@ -84,6 +91,39 @@ App.Presenters.PostPresenter = function(
}).fail(loaded);
}
function attachLinksToPostsAround() {
var searchParams = {query: searchArgs.query, order: searchArgs.order};
promise.wait(postsAroundCalculator.getLinksToPostsAround(searchParams, searchArgs.page, post.id))
.then(function(nextPostUrl, prevPostUrl) {
var $prevPost = $el.find('#post-current-search .right a');
var $nextPost = $el.find('#post-current-search .left a');
if (nextPostUrl) {
$nextPost.addClass('enabled');
$nextPost.attr('href', nextPostUrl);
keyboard.keyup('a', function() {
router.navigate(nextPostUrl);
});
} else {
$nextPost.removeClass('enabled');
$nextPost.removeAttr('href');
keyboard.unbind('a');
}
if (prevPostUrl) {
$prevPost.addClass('enabled');
$prevPost.attr('href', prevPostUrl);
keyboard.keyup('d', function() {
router.navigate(prevPostUrl);
});
} else {
$prevPost.removeClass('enabled');
$prevPost.removeAttr('href');
keyboard.unbind('d');
}
});
}
function refreshPost() {
return promise.make(function(resolve, reject) {
promise.wait(api.get('/posts/' + postNameOrId))
@ -128,6 +168,8 @@ App.Presenters.PostPresenter = function(
presenterManager.initPresenters([
[postCommentListPresenter, _.extend({post: post}, {$target: $el.find('#post-comments-target')})]],
function() { });
attachLinksToPostsAround();
}
function renderSidebar() {
@ -137,6 +179,7 @@ App.Presenters.PostPresenter = function(
function renderPostTemplate() {
return postTemplate({
searchArgs: searchArgs,
post: post,
ownScore: postScore,
postFavorites: postFavorites,
@ -370,6 +413,7 @@ App.DI.register('postPresenter', [
'router',
'keyboard',
'presenterManager',
'postsAroundCalculator',
'postCommentListPresenter',
'topNavigationPresenter',
'messagePresenter'],

View file

@ -47,6 +47,7 @@ App.Presenters.UserListPresenter = function(
var searchArgs = util.parseComplexRouteArgs(args.searchArgs);
searchArgs.order = searchArgs.order || 'name,asc';
searchArgs.page = parseInt(searchArgs.page) || 1;
updateActiveOrder(searchArgs.order);
pagerPresenter.reinit({

View file

@ -39,7 +39,7 @@ App.Router = function(pathJs, _, jQuery, promise, util, appState, presenterManag
inject('#/users(/:searchArgs)', 'userListPresenter');
inject('#/user/:userName(/:tab)', 'userPresenter');
inject('#/posts(/:searchArgs)', 'postListPresenter');
inject('#/post(/:postNameOrId)', 'postPresenter');
inject('#/post(/:postNameOrId)(/:searchArgs)', 'postPresenter');
inject('#/comments(/:searchArgs)', 'globalCommentListPresenter');
inject('#/tags(/:searchArgs)', 'tagListPresenter');
inject('#/help', 'helpPresenter');

View file

@ -0,0 +1,70 @@
var App = App || {};
App.Services = App.Services || {};
App.Services.PostsAroundCalculator = function(_, promise, util, pager) {
pager.init({url: '/posts'});
function resetCache() {
pager.resetCache();
}
function getLinksToPostsAround(searchParams, page, postId) {
return promise.make(function(resolve, reject) {
pager.setSearchParams(searchParams);
pager.setPage(page);
promise.wait(pager.retrieveCached())
.then(function(response) {
var postIds = _.pluck(response.entities, 'id');
var position = _.indexOf(postIds, postId);
if (position === -1) {
resolve(null, null);
}
promise.wait(
getLinkToPostAround(postIds, position, page, -1),
getLinkToPostAround(postIds, position, page, 1))
.then(function(nextPostUrl, prevPostUrl) {
resolve(nextPostUrl, prevPostUrl);
});
});
});
}
function getLinkToPostAround(postIds, position, page, direction) {
return promise.make(function(resolve, reject) {
if (position + direction >= 0 && position + direction < postIds.length) {
var url = util.compileComplexRouteArgs(
'#/post/' + postIds[position + direction],
_.extend({page: page}, pager.getSearchParams()));
resolve(url);
} else if (page + direction >= 1) {
pager.setPage(page + direction);
promise.wait(pager.retrieveCached()).then(function(response) {
if (response.entities.length) {
var post = direction === - 1 ?
_.last(response.entities) :
_.first(response.entities);
var url = util.compileComplexRouteArgs(
'#/post/' + post.id,
_.extend({page: page + direction}, pager.getSearchParams()));
resolve(url);
} else {
resolve(null);
}
});
} else {
resolve(null);
}
});
}
return {
resetCache: resetCache,
getLinksToPostsAround: getLinksToPostsAround,
};
};
App.DI.register('postsAroundCalculator', ['_', 'promise', 'util', 'pager'], App.Services.PostsAroundCalculator);

View file

@ -1,7 +1,11 @@
<div class="post-small post-type-<%= post.contentType %> ">
<a class="link"
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>"
href="#/post/<%= post.id %>">
<% if (typeof(searchArgs) !== 'undefined') { %>
href="#/post/<%= post.id %>/query=<%= searchArgs.query %>;order=<%= searchArgs.order %>;page=<%= searchArgs.page %>"
<% } else { %>
href="#/post/<%= post.id %>"
<% } %>
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>">
<img class="thumb" src="/data/thumbnails/160x160/posts/<%= post.name %>" alt="<%= post.idMarkdown %>"/>

View file

@ -1,5 +1,29 @@
<% var permaLink = (window.location.origin + '/' + window.location.pathname + '/data/posts/' + post.name).replace(/([^:])\/+/g, '$1/') %>
<div id="post-current-search-wrapper">
<div id="post-current-search">
<div class="left">
<a class="enabled">
<i class="fa fa-chevron-left"></i>
Next
</a>
</div>
<div class="search">
<a href="#/posts/query=<%= searchArgs.query %>;order=<%= searchArgs.order %>">
Current search: <%= searchArgs.query || '-' %>
</a>
</div>
<div class="right">
<a class="enabled">
Previous
<i class="fa fa-chevron-right"></i>
</a>
</div>
</div>
</div>
<div id="post-view-wrapper">
<div id="sidebar">
<ul class="essential">