Added prev/next post controls
This commit is contained in:
parent
33c1d99583
commit
688b5b1281
12 changed files with 200 additions and 9 deletions
4
TODO
4
TODO
|
@ -6,10 +6,6 @@ everything related to posts:
|
||||||
- better thumbnail loading
|
- better thumbnail loading
|
||||||
|
|
||||||
- single post view
|
- single post view
|
||||||
- previous and next post (difficult)
|
|
||||||
- remember last search
|
|
||||||
- take care of pages
|
|
||||||
- add A/D hotkeys
|
|
||||||
- editing
|
- editing
|
||||||
- ability to loop video posts
|
- ability to loop video posts
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,25 @@
|
||||||
border: 0;
|
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 {
|
#post-view-wrapper #sidebar {
|
||||||
line-height: 1.33em;
|
line-height: 1.33em;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
|
|
@ -89,6 +89,8 @@
|
||||||
<script type="text/javascript" src="/js/Controls/TagInput.js"></script>
|
<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/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/TopNavigationPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/PagerPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/PagerPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/MessagePresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/MessagePresenter.js"></script>
|
||||||
|
|
|
@ -22,10 +22,15 @@ App.Keyboard = function(mousetrap) {
|
||||||
mousetrap.reset();
|
mousetrap.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unbind(key) {
|
||||||
|
mousetrap.unbind(key);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keydown: keydown,
|
keydown: keydown,
|
||||||
keyup: keyup,
|
keyup: keyup,
|
||||||
reset: reset,
|
reset: reset,
|
||||||
|
unbind: unbind,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ App.Pager = function(
|
||||||
var pageNumber;
|
var pageNumber;
|
||||||
var searchParams;
|
var searchParams;
|
||||||
var url;
|
var url;
|
||||||
|
var cache = {};
|
||||||
|
|
||||||
function init(args) {
|
function init(args) {
|
||||||
url = args.url;
|
url = args.url;
|
||||||
|
@ -55,7 +56,7 @@ App.Pager = function(
|
||||||
|
|
||||||
function retrieve() {
|
function retrieve() {
|
||||||
return promise.make(function(resolve, reject) {
|
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) {
|
.then(function(response) {
|
||||||
var pageSize = response.json.pageSize;
|
var pageSize = response.json.pageSize;
|
||||||
var totalRecords = response.json.totalRecords;
|
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() {
|
function getVisiblePages() {
|
||||||
var pages = [1, totalPages || 1];
|
var pages = [1, totalPages || 1];
|
||||||
var pagesAroundCurrent = 2;
|
var pagesAroundCurrent = 2;
|
||||||
|
@ -100,7 +122,9 @@ App.Pager = function(
|
||||||
getSearchParams: getSearchParams,
|
getSearchParams: getSearchParams,
|
||||||
setSearchParams: setSearchParams,
|
setSearchParams: setSearchParams,
|
||||||
retrieve: retrieve,
|
retrieve: retrieve,
|
||||||
|
retrieveCached: retrieveCached,
|
||||||
getVisiblePages: getVisiblePages,
|
getVisiblePages: getVisiblePages,
|
||||||
|
resetCache: resetCache,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@ App.Presenters.PostListPresenter = function(
|
||||||
topNavigationPresenter.select('posts');
|
topNavigationPresenter.select('posts');
|
||||||
topNavigationPresenter.changeTitle('Posts');
|
topNavigationPresenter.changeTitle('Posts');
|
||||||
searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
||||||
|
searchArgs.page = parseInt(searchArgs.page) || 1;
|
||||||
|
|
||||||
promise.wait(
|
promise.wait(
|
||||||
util.promiseTemplate('post-list'),
|
util.promiseTemplate('post-list'),
|
||||||
|
@ -93,6 +94,7 @@ App.Presenters.PostListPresenter = function(
|
||||||
|
|
||||||
_.each(posts, function(post) {
|
_.each(posts, function(post) {
|
||||||
$target.append(jQuery('<li>' + itemTemplate({
|
$target.append(jQuery('<li>' + itemTemplate({
|
||||||
|
searchArgs: searchArgs,
|
||||||
post: post,
|
post: post,
|
||||||
}) + '</li>'));
|
}) + '</li>'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ App.Presenters.PostPresenter = function(
|
||||||
router,
|
router,
|
||||||
keyboard,
|
keyboard,
|
||||||
presenterManager,
|
presenterManager,
|
||||||
|
postsAroundCalculator,
|
||||||
postCommentListPresenter,
|
postCommentListPresenter,
|
||||||
topNavigationPresenter,
|
topNavigationPresenter,
|
||||||
messagePresenter) {
|
messagePresenter) {
|
||||||
|
@ -23,11 +24,13 @@ App.Presenters.PostPresenter = function(
|
||||||
var postContentTemplate;
|
var postContentTemplate;
|
||||||
var historyTemplate;
|
var historyTemplate;
|
||||||
|
|
||||||
|
var postNameOrId;
|
||||||
|
var searchArgs;
|
||||||
|
|
||||||
var post;
|
var post;
|
||||||
var postScore;
|
var postScore;
|
||||||
var postFavorites;
|
var postFavorites;
|
||||||
var postHistory;
|
var postHistory;
|
||||||
var postNameOrId;
|
|
||||||
|
|
||||||
var privileges = {};
|
var privileges = {};
|
||||||
var editPrivileges = {};
|
var editPrivileges = {};
|
||||||
|
@ -40,6 +43,7 @@ App.Presenters.PostPresenter = function(
|
||||||
|
|
||||||
function init(args, loaded) {
|
function init(args, loaded) {
|
||||||
topNavigationPresenter.select('posts');
|
topNavigationPresenter.select('posts');
|
||||||
|
postsAroundCalculator.resetCache();
|
||||||
|
|
||||||
privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts);
|
privileges.canDeletePosts = auth.hasPrivilege(auth.privileges.deletePosts);
|
||||||
privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts);
|
privileges.canFeaturePosts = auth.hasPrivilege(auth.privileges.featurePosts);
|
||||||
|
@ -76,6 +80,9 @@ App.Presenters.PostPresenter = function(
|
||||||
function reinit(args, loaded) {
|
function reinit(args, loaded) {
|
||||||
postNameOrId = args.postNameOrId;
|
postNameOrId = args.postNameOrId;
|
||||||
|
|
||||||
|
searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
||||||
|
searchArgs.page = parseInt(searchArgs.page) || 1;
|
||||||
|
|
||||||
promise.wait(refreshPost())
|
promise.wait(refreshPost())
|
||||||
.then(function() {
|
.then(function() {
|
||||||
topNavigationPresenter.changeTitle('@' + post.id);
|
topNavigationPresenter.changeTitle('@' + post.id);
|
||||||
|
@ -84,6 +91,39 @@ App.Presenters.PostPresenter = function(
|
||||||
}).fail(loaded);
|
}).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() {
|
function refreshPost() {
|
||||||
return promise.make(function(resolve, reject) {
|
return promise.make(function(resolve, reject) {
|
||||||
promise.wait(api.get('/posts/' + postNameOrId))
|
promise.wait(api.get('/posts/' + postNameOrId))
|
||||||
|
@ -128,6 +168,8 @@ App.Presenters.PostPresenter = function(
|
||||||
presenterManager.initPresenters([
|
presenterManager.initPresenters([
|
||||||
[postCommentListPresenter, _.extend({post: post}, {$target: $el.find('#post-comments-target')})]],
|
[postCommentListPresenter, _.extend({post: post}, {$target: $el.find('#post-comments-target')})]],
|
||||||
function() { });
|
function() { });
|
||||||
|
|
||||||
|
attachLinksToPostsAround();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSidebar() {
|
function renderSidebar() {
|
||||||
|
@ -137,6 +179,7 @@ App.Presenters.PostPresenter = function(
|
||||||
|
|
||||||
function renderPostTemplate() {
|
function renderPostTemplate() {
|
||||||
return postTemplate({
|
return postTemplate({
|
||||||
|
searchArgs: searchArgs,
|
||||||
post: post,
|
post: post,
|
||||||
ownScore: postScore,
|
ownScore: postScore,
|
||||||
postFavorites: postFavorites,
|
postFavorites: postFavorites,
|
||||||
|
@ -370,6 +413,7 @@ App.DI.register('postPresenter', [
|
||||||
'router',
|
'router',
|
||||||
'keyboard',
|
'keyboard',
|
||||||
'presenterManager',
|
'presenterManager',
|
||||||
|
'postsAroundCalculator',
|
||||||
'postCommentListPresenter',
|
'postCommentListPresenter',
|
||||||
'topNavigationPresenter',
|
'topNavigationPresenter',
|
||||||
'messagePresenter'],
|
'messagePresenter'],
|
||||||
|
|
|
@ -47,6 +47,7 @@ App.Presenters.UserListPresenter = function(
|
||||||
|
|
||||||
var searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
var searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
||||||
searchArgs.order = searchArgs.order || 'name,asc';
|
searchArgs.order = searchArgs.order || 'name,asc';
|
||||||
|
searchArgs.page = parseInt(searchArgs.page) || 1;
|
||||||
updateActiveOrder(searchArgs.order);
|
updateActiveOrder(searchArgs.order);
|
||||||
|
|
||||||
pagerPresenter.reinit({
|
pagerPresenter.reinit({
|
||||||
|
|
|
@ -39,7 +39,7 @@ App.Router = function(pathJs, _, jQuery, promise, util, appState, presenterManag
|
||||||
inject('#/users(/:searchArgs)', 'userListPresenter');
|
inject('#/users(/:searchArgs)', 'userListPresenter');
|
||||||
inject('#/user/:userName(/:tab)', 'userPresenter');
|
inject('#/user/:userName(/:tab)', 'userPresenter');
|
||||||
inject('#/posts(/:searchArgs)', 'postListPresenter');
|
inject('#/posts(/:searchArgs)', 'postListPresenter');
|
||||||
inject('#/post(/:postNameOrId)', 'postPresenter');
|
inject('#/post(/:postNameOrId)(/:searchArgs)', 'postPresenter');
|
||||||
inject('#/comments(/:searchArgs)', 'globalCommentListPresenter');
|
inject('#/comments(/:searchArgs)', 'globalCommentListPresenter');
|
||||||
inject('#/tags(/:searchArgs)', 'tagListPresenter');
|
inject('#/tags(/:searchArgs)', 'tagListPresenter');
|
||||||
inject('#/help', 'helpPresenter');
|
inject('#/help', 'helpPresenter');
|
||||||
|
|
70
public_html/js/Services/PostsAroundCalculator.js
Normal file
70
public_html/js/Services/PostsAroundCalculator.js
Normal 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);
|
|
@ -1,7 +1,11 @@
|
||||||
<div class="post-small post-type-<%= post.contentType %> ">
|
<div class="post-small post-type-<%= post.contentType %> ">
|
||||||
<a class="link"
|
<a class="link"
|
||||||
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>"
|
<% if (typeof(searchArgs) !== 'undefined') { %>
|
||||||
href="#/post/<%= post.id %>">
|
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 %>"/>
|
<img class="thumb" src="/data/thumbnails/160x160/posts/<%= post.name %>" alt="<%= post.idMarkdown %>"/>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,29 @@
|
||||||
<% var permaLink = (window.location.origin + '/' + window.location.pathname + '/data/posts/' + post.name).replace(/([^:])\/+/g, '$1/') %>
|
<% 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="post-view-wrapper">
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<ul class="essential">
|
<ul class="essential">
|
||||||
|
|
Loading…
Reference in a new issue