Files
NodeBB/src/controllers/topics.js
Julian Lam 97ce08f5af Removed rel="canonical", closes #3758
On the advice of the following articles:
  - http://googlewebmastercentral.blogspot.com/2013/04/5-common-mistakes-with-relcanonical.html
  - https://moz.com/blog/rel-confused-answers-to-your-rel-canonical-questions

rel="canonical" should not be shown on the same page
as rel="prev" and rel="next" as Google will implicitly
assume that they all point to the same page. With the
"pageless" variety only showing the first page of posts,
it explains exactly why any post content after the first
page is not indexed by Google.

... or perhaps it *is* indexed, but not returned. Who
the heck knows. 😄
2015-11-25 11:27:23 -05:00

329 lines
8.7 KiB
JavaScript

"use strict";
var topicsController = {},
async = require('async'),
S = require('string'),
nconf = require('nconf'),
user = require('../user'),
meta = require('../meta'),
topics = require('../topics'),
posts = require('../posts'),
privileges = require('../privileges'),
plugins = require('../plugins'),
helpers = require('./helpers'),
pagination = require('../pagination'),
utils = require('../../public/src/utils');
topicsController.get = function(req, res, callback) {
var tid = req.params.topic_id,
sort = req.query.sort,
currentPage = parseInt(req.query.page, 10) || 1,
pageCount = 1,
userPrivileges;
if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) {
return callback();
}
async.waterfall([
function (next) {
async.parallel({
privileges: function(next) {
privileges.topics.get(tid, req.uid, next);
},
settings: function(next) {
user.getSettings(req.uid, next);
},
topic: function(next) {
topics.getTopicFields(tid, ['slug', 'postcount', 'deleted'], next);
}
}, next);
},
function (results, next) {
if (!results.topic.slug) {
return callback();
}
userPrivileges = results.privileges;
if (!userPrivileges.read || (parseInt(results.topic.deleted, 10) && !userPrivileges.view_deleted)) {
return helpers.notAllowed(req, res);
}
if ((!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) {
return helpers.redirect(res, '/topic/' + encodeURI(results.topic.slug));
}
var settings = results.settings;
var postCount = parseInt(results.topic.postcount, 10);
pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage));
if (utils.isNumber(req.params.post_index) && (req.params.post_index < 1 || req.params.post_index > postCount)) {
return helpers.redirect(res, '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : ''));
}
if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) {
return callback();
}
var set = 'tid:' + tid + ':posts',
reverse = false;
// `sort` qs has priority over user setting
if (sort === 'newest_to_oldest') {
reverse = true;
} else if (sort === 'most_votes') {
reverse = true;
set = 'tid:' + tid + ':posts:votes';
} else if (settings.topicPostSort === 'newest_to_oldest') {
reverse = true;
} else if (settings.topicPostSort === 'most_votes') {
reverse = true;
set = 'tid:' + tid + ':posts:votes';
}
var postIndex = 0;
req.params.post_index = parseInt(req.params.post_index, 10) || 0;
if (reverse && req.params.post_index === 1) {
req.params.post_index = 0;
}
if (!settings.usePagination) {
if (reverse) {
postIndex = Math.max(0, postCount - (req.params.post_index || postCount) - Math.ceil(settings.postsPerPage / 2));
} else {
postIndex = Math.max(0, (req.params.post_index || 1) - Math.ceil(settings.postsPerPage / 2));
}
} else if (!req.query.page) {
var index;
if (reverse) {
index = Math.max(0, postCount - (req.params.post_index || postCount));
} else {
index = Math.max(0, req.params.post_index - 1) || 0;
}
currentPage = Math.max(1, Math.ceil(index / settings.postsPerPage));
}
var start = (currentPage - 1) * settings.postsPerPage + postIndex,
stop = start + settings.postsPerPage - 1;
topics.getTopicWithPosts(tid, set, req.uid, start, stop, reverse, function (err, topicData) {
if (err && err.message === '[[error:no-topic]]' && !topicData) {
return callback();
}
if (err && !topicData) {
return next(err);
}
if (topicData.category.disabled) {
return callback();
}
topics.modifyByPrivilege(topicData.posts, results.privileges);
plugins.fireHook('filter:controllers.topic.get', topicData, next);
});
},
function (topicData, next) {
var breadcrumbs = [
{
text: topicData.category.name,
url: nconf.get('relative_path') + '/category/' + topicData.category.slug
},
{
text: topicData.title
}
];
helpers.buildCategoryBreadcrumbs(topicData.category.parentCid, function(err, crumbs) {
if (err) {
return next(err);
}
topicData.breadcrumbs = crumbs.concat(breadcrumbs);
next(null, topicData);
});
},
function (topicData, next) {
function findPost(index) {
for(var i=0; i<topicData.posts.length; ++i) {
if (parseInt(topicData.posts[i].index, 10) === parseInt(index, 10)) {
return topicData.posts[i];
}
}
}
var description = '';
var postAtIndex = findPost(Math.max(0, req.params.post_index - 1));
if (postAtIndex && postAtIndex.content) {
description = S(postAtIndex.content).decodeHTMLEntities().stripTags().s;
}
if (description.length > 255) {
description = description.substr(0, 255) + '...';
}
var ogImageUrl = '';
if (topicData.thumb) {
ogImageUrl = topicData.thumb;
} else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture){
ogImageUrl = postAtIndex.user.picture;
} else if (meta.config['brand:logo']) {
ogImageUrl = meta.config['brand:logo'];
} else {
ogImageUrl = '/logo.png';
}
if (typeof ogImageUrl === 'string' && ogImageUrl.indexOf('http') === -1) {
ogImageUrl = nconf.get('url') + ogImageUrl;
}
description = description.replace(/\n/g, ' ');
res.locals.metaTags = [
{
name: "title",
content: topicData.title
},
{
name: "description",
content: description
},
{
property: 'og:title',
content: topicData.title.replace(/&amp;/g, '&')
},
{
property: 'og:description',
content: description
},
{
property: "og:type",
content: 'article'
},
{
property: "og:url",
content: nconf.get('url') + '/topic/' + topicData.slug + (req.params.post_index ? ('/' + req.params.post_index) : ''),
noEscape: true
},
{
property: 'og:image',
content: ogImageUrl,
noEscape: true
},
{
property: "og:image:url",
content: ogImageUrl,
noEscape: true
},
{
property: "article:published_time",
content: utils.toISOString(topicData.timestamp)
},
{
property: 'article:modified_time',
content: utils.toISOString(topicData.lastposttime)
},
{
property: 'article:section',
content: topicData.category ? topicData.category.name : ''
}
];
res.locals.linkTags = [
{
rel: 'alternate',
type: 'application/rss+xml',
href: nconf.get('url') + '/topic/' + tid + '.rss'
}
];
if (topicData.category) {
res.locals.linkTags.push({
rel: 'up',
href: nconf.get('url') + '/category/' + topicData.category.slug
});
}
next(null, topicData);
}
], function (err, data) {
if (err) {
return callback(err);
}
data.privileges = userPrivileges;
data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
data.pagination = pagination.create(currentPage, pageCount);
data.pagination.rel.forEach(function(rel) {
rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href;
res.locals.linkTags.push(rel);
});
topics.increaseViewCount(tid);
if (req.uid) {
topics.markAsRead([tid], req.uid, function(err, markedRead) {
if (err) {
return callback(err);
}
if (markedRead) {
topics.pushUnreadCount(req.uid);
topics.markTopicNotificationsRead(tid, req.uid);
}
});
}
plugins.fireHook('filter:topic.build', {req: req, res: res, templateData: data}, function(err, data) {
if (err) {
return callback(err);
}
res.render('topic', data.templateData);
});
});
};
topicsController.teaser = function(req, res, next) {
var tid = req.params.topic_id;
if (!utils.isNumber(tid)) {
return next(new Error('[[error:invalid-tid]]'));
}
async.waterfall([
function(next) {
privileges.topics.can('read', tid, req.uid, next);
},
function(canRead, next) {
if (!canRead) {
return res.status(403).json('[[error:no-privileges]]');
}
topics.getLatestUndeletedPid(tid, next);
},
function(pid, next) {
if (!pid) {
return res.status(404).json('not-found');
}
posts.getPostSummaryByPids([pid], req.uid, {stripTags: false}, next);
}
], function(err, posts) {
if (err) {
return next(err);
}
if (!Array.isArray(posts) || !posts.length) {
return res.status(404).json('not-found');
}
res.json(posts[0]);
});
};
module.exports = topicsController;