diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 8fc3becbb5..7b5a6eece9 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -36,12 +36,13 @@ "topic-locked": "Topic Locked", - "still-uploading" : "Please wait for uploads to complete.", - "content-too-short" : "Please enter a longer post. At least %1 characters.", - "title-too-short" : "Please enter a longer title. At least %1 characters.", - "title-too-long" : "Please enter a shorter title. Titles can't be longer than %1 characters.", - "too-many-posts" : "You can only post every %1 seconds.", - "file-too-big" : "Maximum allowed file size is %1 kbs", + "still-uploading": "Please wait for uploads to complete.", + "content-too-short": "Please enter a longer post. At least %1 characters.", + "title-too-short": "Please enter a longer title. At least %1 characters.", + "title-too-long": "Please enter a shorter title. Titles can't be longer than %1 characters.", + "invalid-title": "Invalid title!", + "too-many-posts": "You can only post every %1 seconds.", + "file-too-big": "Maximum allowed file size is %1 kbs", "cant-vote-self-post": "You cannot vote for your own post", "already-favourited": "You already favourited this post", diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index d50e4b7e56..0c7234782a 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -47,8 +47,9 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ addBlockquoteEllipses($('.topic .post-content > blockquote')); var bookmark = localStorage.getItem('topic:' + tid + ':bookmark'); - if (window.location.hash) { - Topic.scrollToPost(window.location.hash.substr(1), true); + var postIndex = getPostIndex(); + if (postIndex) { + Topic.scrollToPost(postIndex, true); } else if (bookmark && (!config.usePagination || (config.usePagination && pagination.currentPage === 1)) && postCount > 1) { app.alert({ alert_id: 'bookmark', @@ -64,9 +65,7 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ }); } - if (!config.usePagination) { - navigator.init('.posts > .post-row', postCount, Topic.navigatorCallback); - } + navigator.init('.posts > .post-row', postCount, Topic.navigatorCallback); socket.on('event:new_post', onNewPost); @@ -78,6 +77,11 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ socket.emit('topics.increaseViewCount', tid); }; + function getPostIndex() { + var parts = window.location.pathname.split('/'); + return parts[4] ? (parseInt(parts[4], 10) - 1) : ''; + } + function showBottomPostBar() { if($('#post-container .post-row').length > 1 || !$('#post-container li[data-index="0"]').length) { $('.bottom-post-bar').removeClass('hide'); @@ -142,78 +146,80 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ } Topic.navigatorCallback = function(element) { - var pid = element.attr('data-pid'); + var postIndex = parseInt(element.attr('data-index'), 10); var currentBookmark = localStorage.getItem('topic:' + ajaxify.variables.get('topic_id') + ':bookmark'); - if (!currentBookmark || parseInt(pid, 10) >= parseInt(currentBookmark, 10)) { - localStorage.setItem('topic:' + ajaxify.variables.get('topic_id') + ':bookmark', pid); + if (!currentBookmark || parseInt(postIndex, 10) >= parseInt(currentBookmark, 10)) { + localStorage.setItem('topic:' + ajaxify.variables.get('topic_id') + ':bookmark', postIndex); app.removeAlert('bookmark'); } if (!scrollingToPost) { - - var newUrl = window.location.href.replace(window.location.hash, '') + '#' + pid; + var parts = window.location.pathname.split('/'); + var topicId = parts[2], + slug = parts[3]; + var newUrl = 'topic/' + topicId + '/' + (slug ? slug : ''); + if (postIndex > 0) { + newUrl += '/' + (postIndex + 1); + } if (newUrl !== currentUrl) { if (history.replaceState) { + var search = (window.location.search ? window.location.search : ''); history.replaceState({ - url: window.location.pathname.slice(1) + (window.location.search ? window.location.search : '' ) + '#' + pid - }, null, newUrl); + url: newUrl + search + }, null, window.location.protocol + '//' + window.location.host + '/' + newUrl + search); } currentUrl = newUrl; } } }; - Topic.scrollToPost = function(pid, highlight, duration, offset) { - if (!pid) { + Topic.scrollToPost = function(postIndex, highlight, duration, offset) { + if (!postIndex) { return; } - if(!offset) { + if (!offset) { offset = 0; } scrollingToPost = true; - if($('#post_anchor_' + pid).length) { - return scrollToPid(pid); + if($('#post_anchor_' + postIndex).length) { + return scrollToPid(postIndex); } if(config.usePagination) { - socket.emit('posts.getPidPage', pid, function(err, page) { - if(err) { - return; - } - if(parseInt(page, 10) !== pagination.currentPage) { - pagination.loadPage(page, function() { - scrollToPid(pid); - }); - } else { - scrollToPid(pid); - } - }); - } else { - socket.emit('posts.getPidIndex', pid, function(err, index) { - if(err) { - return; - } + if (window.location.search.indexOf('page') !== -1) { + navigator.update(); + scrollingToPost = false; + return; + } - $('#post-container').empty(); - var after = index - config.postsPerPage + 1; - if(after < 0) { - after = 0; - } + var page = Math.ceil((postIndex + 1) / config.postsPerPage) - loadPostsAfter(after, function() { - scrollToPid(pid); + if(parseInt(page, 10) !== pagination.currentPage) { + pagination.loadPage(page, function() { + scrollToPid(postIndex); }); + } else { + scrollToPid(postIndex); + } + } else { + $('#post-container').empty(); + var after = postIndex - config.postsPerPage + 1; + if(after < 0) { + after = 0; + } + loadPostsAfter(after, function() { + scrollToPid(postIndex); }); } - function scrollToPid(pid) { - var scrollTo = $('#post_anchor_' + pid), + function scrollToPid(postIndex) { + var scrollTo = $('#post_anchor_' + postIndex), tid = $('#post-container').attr('data-tid'); function animateScroll() { @@ -235,9 +241,8 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ } } - if (tid && scrollTo.length) { - if($('#post-container li.post-row[data-pid="' + pid + '"]').attr('data-index') !== '0') { + if($('#post-container li.post-row[data-index="' + postIndex + '"]').attr('data-index') !== '0') { animateScroll(); } else { navigator.update(); @@ -362,14 +367,14 @@ define('forum/topic', ['forum/pagination', 'forum/infinitescroll', 'forum/topic/ } function loadMorePosts(direction) { - if (!$('#post-container').length) { + if (!$('#post-container').length || scrollingToPost) { return; } infinitescroll.calculateAfter(direction, '#post-container .post-row', config.postsPerPage, function(after, offset, el) { loadPostsAfter(after, function() { if (direction < 0 && el) { - Topic.scrollToPost(el.attr('data-pid'), false, 0, offset); + Topic.scrollToPost(el.attr('data-index'), false, 0, offset); } }); }); diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index d08f7d24c8..fa1f233c00 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -441,7 +441,7 @@ define('composer', ['taskbar', 'composer/controls', 'composer/uploads', 'compose titleEl.val(titleEl.val().trim()); bodyEl.val(bodyEl.val().trim()); - if(thumbEl.length) { + if (thumbEl.length) { thumbEl.val(thumbEl.val().trim()); } @@ -453,6 +453,8 @@ define('composer', ['taskbar', 'composer/controls', 'composer/uploads', 'compose return composerAlert('[[error:title-too-short, ' + config.minimumTitleLength + ']]'); } else if (checkTitle && titleEl.val().length > parseInt(config.maximumTitleLength, 10)) { return composerAlert('[[error:title-too-long, ' + config.maximumTitleLength + ']]'); + } else if (checkTitle && !utils.slugify(titleEl.val()).length) { + return composerAlert('[[error:invalid-title]]'); } else if (bodyEl.val().length < parseInt(config.minimumPostLength, 10)) { return composerAlert('[[error:content-too-short, ' + config.minimumPostLength + ']]'); } diff --git a/public/src/modules/share.js b/public/src/modules/share.js index 2cbb16889e..41f789d826 100644 --- a/public/src/modules/share.js +++ b/public/src/modules/share.js @@ -10,8 +10,8 @@ define('share', function() { var baseUrl = window.location.protocol + '//' + window.location.host; - function openShare(url, hash, width, height) { - window.open(url + encodeURIComponent(baseUrl + window.location.pathname + hash), '_blank', 'width=' + width + ',height=' + height + ',scrollbars=no,status=no'); + function openShare(url, urlToPost, width, height) { + window.open(url + encodeURIComponent(baseUrl + urlToPost), '_blank', 'width=' + width + ',height=' + height + ',scrollbars=no,status=no'); return false; } @@ -32,15 +32,15 @@ define('share', function() { }); addHandler('.twitter-share', function () { - return openShare('https://twitter.com/intent/tweet?text=' + name + '&url=', getPostHash($(this)), 550, 420); + return openShare('https://twitter.com/intent/tweet?text=' + name + '&url=', getPostUrl($(this)), 550, 420); }); addHandler('.facebook-share', function () { - return openShare('https://www.facebook.com/sharer/sharer.php?u=', getPostHash($(this)), 626, 436); + return openShare('https://www.facebook.com/sharer/sharer.php?u=', getPostUrl($(this)), 626, 436); }); addHandler('.google-share', function () { - return openShare('https://plus.google.com/share?url=', getPostHash($(this)), 500, 570); + return openShare('https://plus.google.com/share?url=', getPostUrl($(this)), 500, 570); }); }; @@ -49,11 +49,9 @@ define('share', function() { } function getPostHash(clickedElement) { - var pid = clickedElement.parents('.post-row').attr('data-pid'); - if (pid) { - return '#' + pid; - } - return ''; + var parts = window.location.pathname.split('/'); + var postIndex = parseInt(clickedElement.parents('.post-row').attr('data-index'), 10); + return '/topic/' + parts[2] + (parts[3] ? '/' + parts[3] : '') + (postIndex ? '/' + (postIndex + 1) : ''); } return module; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index f9a87d2d79..4c0cba514b 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -20,7 +20,7 @@ topicsController.get = function(req, res, next) { userPrivileges; async.waterfall([ - function(next) { + function (next) { privileges.topics.get(tid, uid, function(err, privileges) { if (err) { return next(err); @@ -40,8 +40,16 @@ topicsController.get = function(req, res, next) { return next(err); } - var start = (page - 1) * settings.postsPerPage, - end = start + settings.postsPerPage - 1; + var postIndex = 0; + if (!settings.usePagination) { + postIndex = Math.max((req.params.post_index || 1) - (settings.postsPerPage), 0); + } else if (!req.query.page) { + var index = Math.max(parseInt((req.params.post_index || 0), 10), 0); + page = Math.ceil((index + 1) / settings.postsPerPage); + } + + var start = (page - 1) * settings.postsPerPage + postIndex, + end = start + settings.postsPerPage - 1 + postIndex; topics.getTopicWithPosts(tid, uid, start, end, function (err, topicData) { if (topicData) { diff --git a/src/posts.js b/src/posts.js index ffd1a3b61a..69f555a05c 100644 --- a/src/posts.js +++ b/src/posts.js @@ -274,6 +274,9 @@ var db = require('./database'), } postTools.parse(post.content, next); + }, + index: function(next) { + Posts.getPidIndex(post.pid, next); } }, function(err, results) { if (err) { @@ -283,6 +286,7 @@ var db = require('./database'), post.user = results.user; post.topic = results.topicCategory.topic; post.category = results.topicCategory.category; + post.index = parseInt(results.index, 10) + 1; if (stripTags) { var s = S(results.content); diff --git a/src/routes/index.js b/src/routes/index.js index e719852ab2..a1fd3d1965 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -51,6 +51,9 @@ function staticRoutes(app, middleware, controllers) { function topicRoutes(app, middleware, controllers) { app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); + app.get('/topic/:topic_id/:slug/:post_index?', middleware.buildHeader, middleware.addSlug, controllers.topics.get); + app.get('/api/topic/:topic_id/:slug/:post_index?', controllers.topics.get); + app.get('/topic/:topic_id/:slug?', middleware.buildHeader, middleware.addSlug, controllers.topics.get); app.get('/api/topic/:topic_id/:slug?', controllers.topics.get); } diff --git a/src/topics.js b/src/topics.js index 76b01ae036..05b57f5d33 100644 --- a/src/topics.js +++ b/src/topics.js @@ -312,29 +312,40 @@ var async = require('async'), Topics.getTeaser = function(tid, callback) { Topics.getLatestUndeletedPid(tid, function(err, pid) { - if (err) { + if (err || !pid) { return callback(err); } - if (!pid) { - return callback(null, null); - } + async.parallel({ + postData: function(next) { + posts.getPostFields(pid, ['pid', 'uid', 'timestamp'], function(err, postData) { + if (err) { + return next(err); + } else if(!postData || !utils.isNumber(postData.uid)) { + return next(new Error('[[error:no-teaser]]')); + } - posts.getPostFields(pid, ['pid', 'uid', 'timestamp'], function(err, postData) { + user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) { + if (err) { + return next(err); + } + postData.user = userData; + next(null, postData); + }); + }); + }, + postIndex: function(next) { + posts.getPidIndex(pid, next); + } + }, function(err, results) { if (err) { return callback(err); - } else if(!postData || !utils.isNumber(postData.uid)) { - return callback(new Error('[[error:no-teaser]]')); } - user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) { - if (err) { - return callback(err); - } - postData.timestamp = utils.toISOString(postData.timestamp); - postData.user = userData; - callback(null, postData); - }); + results.postData.timestamp = utils.toISOString(results.postData.timestamp); + results.postData.index = parseInt(results.postIndex, 10) + 1; + + callback(null, results.postData); }); }); }; diff --git a/src/topics/create.js b/src/topics/create.js index 210545d690..7bf23b4bed 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -24,9 +24,15 @@ module.exports = function(Topics) { return callback(err); } - var slug = tid + '/' + utils.slugify(title), + var slug = utils.slugify(title), timestamp = Date.now(); + if (!slug.length) { + return callback(new Error('[[error:invalid-title]]')); + } + + slug = tid + '/' + slug; + var topicData = { 'tid': tid, 'uid': uid, diff --git a/src/topics/posts.js b/src/topics/posts.js index d836fc7536..0f81a1f8e6 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -29,7 +29,7 @@ module.exports = function(Topics) { if (Array.isArray(postData) && !postData.length) { return callback(null, []); } - + start = parseInt(start, 10); for(var i=0; i