mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-11 02:51:58 +02:00
Merge remote-tracking branch 'origin/master' into sortable-menu
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.",
|
||||
"email-not-confirmed-chat": "You are unable to chat until your email is confirmed",
|
||||
"no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email",
|
||||
"email-confirm-failed": "We could not confirm your email, please try again later.",
|
||||
|
||||
"username-too-short": "Username too short",
|
||||
"username-too-long": "Username too long",
|
||||
@@ -44,6 +45,7 @@
|
||||
"category-disabled": "Category disabled",
|
||||
|
||||
"topic-locked": "Topic Locked",
|
||||
"post-edit-duration-expired": "You are only allowed to edit posts for %1 seconds after posting",
|
||||
|
||||
"still-uploading": "Please wait for uploads to complete.",
|
||||
"content-too-short": "Please enter a longer post. Posts should contain at least %1 characters.",
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
"email-confirmed": "Email Confirmed",
|
||||
"email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",
|
||||
"email-confirm-error": "An error occurred...",
|
||||
"email-confirm-error-message": "There was a problem validating your email address. Perhaps the code was invalid or has expired.",
|
||||
"email-confirm-sent": "Confirmation email sent."
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
var ajaxify = ajaxify || {};
|
||||
var ajaxify = ajaxify || {
|
||||
isPopState: false
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
require(['templates'], function (templatesModule) {
|
||||
require(['templates', 'ajaxifyCache'], function (templatesModule, cache) {
|
||||
/*global app, templates, utils, socket, translator, config, RELATIVE_PATH*/
|
||||
|
||||
var location = document.location || window.location,
|
||||
@@ -13,7 +15,9 @@ $(document).ready(function() {
|
||||
|
||||
window.onpopstate = function (event) {
|
||||
if (event !== null && event.state && event.state.url !== undefined && !ajaxify.initialLoad) {
|
||||
ajaxify.isPopState = true;
|
||||
ajaxify.go(event.state.url, function() {
|
||||
ajaxify.isPopState = false;
|
||||
$(window).trigger('action:popstate', {url: event.state.url});
|
||||
}, true);
|
||||
}
|
||||
@@ -55,6 +59,10 @@ $(document).ready(function() {
|
||||
// "quiet": If set to true, will not call pushState
|
||||
app.enterRoom('');
|
||||
|
||||
// If the url is in the cache, load from cache instead
|
||||
if (cache.get(url, callback)) { return true; }
|
||||
else { cache.url = ajaxify.currentPage; }
|
||||
|
||||
$(window).off('scroll');
|
||||
|
||||
if ($('#content').hasClass('ajaxifying') && apiXHR) {
|
||||
@@ -114,6 +122,7 @@ $(document).ready(function() {
|
||||
templates.parse(tpl_url, data, function(template) {
|
||||
translator.translate(template, function(translatedTemplate) {
|
||||
setTimeout(function() {
|
||||
cache.set();
|
||||
$('#content').html(translatedTemplate);
|
||||
|
||||
ajaxify.variables.parse();
|
||||
|
||||
@@ -12,6 +12,8 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'],
|
||||
});
|
||||
|
||||
Unread.init = function() {
|
||||
var topicsContainer = $('#topics-container');
|
||||
|
||||
app.enterRoom('recent_posts');
|
||||
|
||||
$('#new-topics-alert').on('click', function() {
|
||||
@@ -42,7 +44,7 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'],
|
||||
|
||||
app.alertSuccess('[[unread:topics_marked_as_read.success]]');
|
||||
|
||||
$('#topics-container').empty();
|
||||
topicsContainer.empty();
|
||||
$('#category-no-topics').removeClass('hidden');
|
||||
$('.markread').addClass('hidden');
|
||||
});
|
||||
@@ -68,11 +70,22 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'],
|
||||
});
|
||||
});
|
||||
|
||||
topicsContainer.on('click', '.topic-title, .replies a', function(e) {
|
||||
var tid = $(e.target).parents('[data-tid]').attr('data-tid');
|
||||
socket.emit('topics.markAsRead', [tid], function(err) {
|
||||
if(err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
doneRemovingTids([tid], true);
|
||||
});
|
||||
});
|
||||
|
||||
socket.emit('categories.get', onCategoriesLoaded);
|
||||
|
||||
topicSelect.init();
|
||||
|
||||
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20) {
|
||||
if ($("body").height() <= $(window).height() && topicsContainer.children().length >= 20) {
|
||||
$('#load-more-btn').show();
|
||||
}
|
||||
|
||||
@@ -83,16 +96,16 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'],
|
||||
infinitescroll.init(loadMoreTopics);
|
||||
|
||||
function loadMoreTopics(direction) {
|
||||
if(direction < 0 || !$('#topics-container').length) {
|
||||
if(direction < 0 || !topicsContainer.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
infinitescroll.loadMore('topics.loadMoreUnreadTopics', {
|
||||
after: $('#topics-container').attr('data-nextstart')
|
||||
after: topicsContainer.attr('data-nextstart')
|
||||
}, function(data, done) {
|
||||
if (data.topics && data.topics.length) {
|
||||
recent.onTopicsLoaded('unread', data.topics, true, done);
|
||||
$('#topics-container').attr('data-nextstart', data.nextStart);
|
||||
topicsContainer.attr('data-nextstart', data.nextStart);
|
||||
} else {
|
||||
done();
|
||||
$('#load-more-btn').hide();
|
||||
@@ -101,10 +114,10 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'],
|
||||
}
|
||||
};
|
||||
|
||||
function doneRemovingTids(tids) {
|
||||
function doneRemovingTids(tids, quiet) {
|
||||
removeTids(tids);
|
||||
|
||||
app.alertSuccess('[[unread:topics_marked_as_read.success]]');
|
||||
if (!quiet) { app.alertSuccess('[[unread:topics_marked_as_read.success]]'); }
|
||||
|
||||
if (!$('#topics-container').children().length) {
|
||||
$('#category-no-topics').removeClass('hidden');
|
||||
@@ -123,25 +136,20 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll'],
|
||||
}
|
||||
|
||||
function createCategoryLinks(categories) {
|
||||
categories = categories.filter(function(category) {
|
||||
return !category.disabled;
|
||||
});
|
||||
|
||||
for(var i=0; i<categories.length; ++i) {
|
||||
for (var i=0; i<categories.length; ++i) {
|
||||
createCategoryLink(categories[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function createCategoryLink(category) {
|
||||
|
||||
var link = $('<a role="menuitem" href="#"></a>');
|
||||
|
||||
if (category.icon) {
|
||||
link.append('<i class="fa fa-fw ' + category.icon + '"></i> ' + category.name);
|
||||
} else {
|
||||
link.append(category.name);
|
||||
}
|
||||
|
||||
|
||||
$('<li role="presentation" class="category" data-cid="' + category.cid + '"></li>')
|
||||
.append(link)
|
||||
.appendTo($('.markread .dropdown-menu'));
|
||||
|
||||
37
public/src/modules/ajaxifyCache.js
Normal file
37
public/src/modules/ajaxifyCache.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
/* globals define, app, ajaxify */
|
||||
|
||||
define('ajaxifyCache', function() {
|
||||
var Cache = {
|
||||
url: undefined,
|
||||
DOM: undefined,
|
||||
tempDOM: undefined
|
||||
};
|
||||
|
||||
Cache.set = function() {
|
||||
Cache.DOM = $('#content > *').detach();
|
||||
};
|
||||
|
||||
Cache.get = function(url, callback) {
|
||||
if (url === Cache.url && ajaxify.isPopState) {
|
||||
// Swap DOM elements
|
||||
// setTimeout(function() {
|
||||
Cache.tempDOM = $('#content > *').detach();
|
||||
$('#content').append(Cache.DOM);
|
||||
Cache.DOM = Cache.tempDOM;
|
||||
// }, 100); // 100ms for realism! :sunglasses:
|
||||
|
||||
// Set the values that normally get set on ajaxify
|
||||
Cache.url = ajaxify.currentPage;
|
||||
ajaxify.currentPage = url;
|
||||
|
||||
if (typeof callback === 'function') { callback(); }
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return Cache;
|
||||
});
|
||||
@@ -59,11 +59,6 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar,
|
||||
});
|
||||
})(userObj);
|
||||
}
|
||||
|
||||
var seeAll = '<li class="pagelink"><a href="' + config.relative_path + '/chats">[[modules:chat.see_all]]</a></li>';
|
||||
translator.translate(seeAll, function(translated) {
|
||||
$(translated).appendTo(chatsListEl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -58,10 +58,9 @@ define('notifications', ['sounds'], function(sound) {
|
||||
});
|
||||
|
||||
notifList.on('click', '.mark-read', function(e) {
|
||||
var anchorEl = $(this.parentNode),
|
||||
parentEl = anchorEl.parent(),
|
||||
nid = anchorEl.attr('data-nid'),
|
||||
unread = parentEl.hasClass('unread');
|
||||
var liEl = $(this.parentNode),
|
||||
nid = liEl.siblings('a').attr('data-nid'),
|
||||
unread = liEl.hasClass('unread');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -71,7 +70,7 @@ define('notifications', ['sounds'], function(sound) {
|
||||
app.alertError(err.message);
|
||||
}
|
||||
|
||||
parentEl.toggleClass('unread');
|
||||
liEl.toggleClass('unread');
|
||||
increaseNotifCount(unread ? -1 : 1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,13 +121,7 @@ var async = require('async'),
|
||||
},
|
||||
function(cids, next) {
|
||||
Categories.getCategories(cids, uid, next);
|
||||
},
|
||||
function(categories, next) {
|
||||
categories = categories.filter(function(category) {
|
||||
return !category.disabled;
|
||||
});
|
||||
next(null, categories);
|
||||
}
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -286,10 +280,9 @@ var async = require('async'),
|
||||
Categories.getCategoriesData(cids, next);
|
||||
},
|
||||
function (categories, next) {
|
||||
// Filter categories to isolate children, and remove disabled categories
|
||||
async.map(cids, function(cid, next) {
|
||||
next(null, categories.filter(function(category) {
|
||||
return category && parseInt(category.parentCid, 10) === parseInt(cid, 10) && !category.disabled;
|
||||
return category && parseInt(category.parentCid, 10) === parseInt(cid, 10);
|
||||
}));
|
||||
}, next);
|
||||
}
|
||||
|
||||
@@ -119,9 +119,10 @@ Controllers.register = function(req, res, next) {
|
||||
|
||||
|
||||
Controllers.confirmEmail = function(req, res, next) {
|
||||
user.email.confirm(req.params.code, function (data) {
|
||||
data.status = data.status === 'ok';
|
||||
res.render('confirm', data);
|
||||
user.email.confirm(req.params.code, function (err) {
|
||||
res.render('confirm', {
|
||||
error: err ? err.message : ''
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ module.exports = function(privileges) {
|
||||
});
|
||||
|
||||
async.parallel({
|
||||
categories: function(next) {
|
||||
categories.getMultipleCategoryFields(cids, ['disabled'], next);
|
||||
},
|
||||
allowedTo: function(next) {
|
||||
helpers.isUserAllowedTo(privilege, uid, cids, next);
|
||||
},
|
||||
@@ -98,6 +101,10 @@ module.exports = function(privileges) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
cids = cids.filter(function(cid, index) {
|
||||
return !results.categories[index].disabled;
|
||||
});
|
||||
|
||||
if (results.isAdmin) {
|
||||
return callback(null, cids);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
var async = require('async'),
|
||||
winston = require('winston'),
|
||||
|
||||
meta = require('../meta'),
|
||||
posts = require('../posts'),
|
||||
topics = require('../topics'),
|
||||
user = require('../user'),
|
||||
@@ -108,27 +109,24 @@ module.exports = function(privileges) {
|
||||
};
|
||||
|
||||
privileges.posts.canEdit = function(pid, uid, callback) {
|
||||
helpers.some([
|
||||
function(next) {
|
||||
isPostTopicLocked(pid, function(err, isLocked) {
|
||||
if (err || isLocked) {
|
||||
return next(err, false);
|
||||
}
|
||||
|
||||
helpers.some([
|
||||
function(next) {
|
||||
posts.isOwner(pid, uid, next);
|
||||
},
|
||||
function(next) {
|
||||
helpers.hasEnoughReputationFor(['privileges:manage_content', 'privileges:manage_topic'], uid, next);
|
||||
}
|
||||
], next);
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
isAdminOrMod(pid, uid, next);
|
||||
async.parallel({
|
||||
isEditable: async.apply(isPostEditable, pid, uid),
|
||||
isAdminOrMod: async.apply(isAdminOrMod, pid, uid)
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
], callback);
|
||||
if (results.isAdminOrMod) {
|
||||
return callback(null, true);
|
||||
}
|
||||
if (results.isEditable.isLocked) {
|
||||
return callback(new Error('[[error:topic-locked]]]'));
|
||||
}
|
||||
if (results.isEditable.isEditExpired) {
|
||||
return callback(new Error('[[error:post-edit-duration-expired, ' + meta.config.postEditDuration + ']]'));
|
||||
}
|
||||
callback(null, results.isEditable.editable);
|
||||
});
|
||||
};
|
||||
|
||||
privileges.posts.canMove = function(pid, uid, callback) {
|
||||
@@ -140,6 +138,36 @@ module.exports = function(privileges) {
|
||||
});
|
||||
};
|
||||
|
||||
function isPostEditable(pid, uid, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
posts.getPostFields(pid, ['tid', 'timestamp'], next);
|
||||
},
|
||||
function(postData, next) {
|
||||
var postEditDuration = parseInt(meta.config.postEditDuration, 10);
|
||||
if (postEditDuration && Date.now() - parseInt(postData.timestamp, 10) > postEditDuration * 1000) {
|
||||
return callback(null, {isEditExpired: true});
|
||||
}
|
||||
topics.isLocked(postData.tid, next);
|
||||
},
|
||||
function(isLocked, next) {
|
||||
if (isLocked) {
|
||||
return callback(null, {isLocked: true});
|
||||
}
|
||||
helpers.some([
|
||||
function(next) {
|
||||
posts.isOwner(pid, uid, next);
|
||||
},
|
||||
function(next) {
|
||||
helpers.hasEnoughReputationFor(['privileges:manage_content', 'privileges:manage_topic'], uid, next);
|
||||
}
|
||||
], function(err, editable) {
|
||||
next(err, {editable: editable});
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
function isPostTopicLocked(pid, callback) {
|
||||
posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
if (err) {
|
||||
|
||||
@@ -58,14 +58,14 @@ function searchInContent(query, data, callback) {
|
||||
async.parallel({
|
||||
pids: function(next) {
|
||||
if (data.searchIn === 'posts' || data.searchIn === 'titlesposts') {
|
||||
searchQuery('post', query, next);
|
||||
search.searchQuery('post', query, next);
|
||||
} else {
|
||||
next(null, []);
|
||||
}
|
||||
},
|
||||
tids: function(next) {
|
||||
if (data.searchIn === 'titles' || data.searchIn === 'titlesposts') {
|
||||
searchQuery('topic', query, next);
|
||||
search.searchQuery('topic', query, next);
|
||||
} else {
|
||||
next(null, []);
|
||||
}
|
||||
@@ -93,6 +93,7 @@ function searchInContent(query, data, callback) {
|
||||
mainPids.push(pid);
|
||||
}
|
||||
});
|
||||
|
||||
privileges.posts.filter('read', mainPids, data.uid, next);
|
||||
},
|
||||
function(pids, next) {
|
||||
@@ -475,10 +476,10 @@ function getMainPids(tids, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function searchQuery(index, query, callback) {
|
||||
search.searchQuery = function(index, query, callback) {
|
||||
plugins.fireHook('filter:search.query', {
|
||||
index: index,
|
||||
query: query
|
||||
}, callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ var async = require('async'),
|
||||
require('./topics/follow')(Topics);
|
||||
require('./topics/tags')(Topics);
|
||||
require('./topics/teaser')(Topics);
|
||||
require('./topics/suggested')(Topics);
|
||||
|
||||
Topics.exists = function(tid, callback) {
|
||||
db.isSortedSetMember('topics:tid', tid, callback);
|
||||
|
||||
74
src/topics/suggested.js
Normal file
74
src/topics/suggested.js
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
_ = require('underscore'),
|
||||
|
||||
categories = require('../categories'),
|
||||
search = require('../search'),
|
||||
db = require('../database');
|
||||
|
||||
|
||||
module.exports = function(Topics) {
|
||||
|
||||
Topics.getSuggestedTopics = function(tid, uid, start, end, callback) {
|
||||
async.parallel({
|
||||
tagTids: function(next) {
|
||||
getTidsWithSameTags(tid, next);
|
||||
},
|
||||
searchTids: function(next) {
|
||||
getSearchTids(tid, next);
|
||||
},
|
||||
categoryTids: function(next) {
|
||||
getCategoryTids(tid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var tids = results.tagTids.concat(results.searchTids).concat(results.categoryTids);
|
||||
tids = tids.filter(function(_tid, index, array) {
|
||||
return parseInt(_tid, 10) !== parseInt(tid, 10) && array.indexOf(_tid) === index;
|
||||
}).slice(start, end + 1);
|
||||
|
||||
Topics.getTopics(tids, uid, callback);
|
||||
});
|
||||
};
|
||||
|
||||
function getTidsWithSameTags(tid, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
Topics.getTopicTags(tid, next);
|
||||
},
|
||||
function(tags, next) {
|
||||
async.map(tags, function(tag, next) {
|
||||
Topics.getTagTids(tag, 0, -1, next);
|
||||
}, next);
|
||||
},
|
||||
function(data, next) {
|
||||
next(null, _.unique(_.flatten(data)));
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
function getSearchTids(tid, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
Topics.getTopicField(tid, 'title', next);
|
||||
},
|
||||
function(title, next) {
|
||||
search.searchQuery('topic', title, next);
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
function getCategoryTids(tid, callback) {
|
||||
Topics.getTopicField(tid, 'cid', function(err, cid) {
|
||||
if (err || !cid) {
|
||||
return callback(err, []);
|
||||
}
|
||||
categories.getTopicIds('cid:' + cid + ':tids', true, 0, 9, callback);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -87,21 +87,18 @@ var async = require('async'),
|
||||
UserEmail.confirm = function(code, callback) {
|
||||
db.getObject('confirm:' + code, function(err, confirmObj) {
|
||||
if (err) {
|
||||
return callback({
|
||||
status:'error'
|
||||
});
|
||||
return callback(new Error('[[error:parse-error]]'));
|
||||
}
|
||||
|
||||
if (confirmObj && confirmObj.uid && confirmObj.email) {
|
||||
user.setUserField(confirmObj.uid, 'email:confirmed', 1, function() {
|
||||
callback({
|
||||
status: 'ok'
|
||||
});
|
||||
async.series([
|
||||
async.apply(user.setUserField, confirmObj.uid, 'email:confirmed', 1),
|
||||
async.apply(db.delete, 'confirm:' + code)
|
||||
], function(err) {
|
||||
callback(err ? new Error('[[error:email-confirm-failed]]') : null);
|
||||
});
|
||||
} else {
|
||||
callback({
|
||||
status: 'not_ok'
|
||||
});
|
||||
callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,6 +39,20 @@ var async = require('async'),
|
||||
});
|
||||
};
|
||||
|
||||
UserNotifications.getAll = function(uid, count, callback) {
|
||||
getNotifications(uid, count, function(err, notifs) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
notifs = notifs.unread.concat(notifs.read);
|
||||
notifs = notifs.filter(Boolean).sort(function(a, b) {
|
||||
return b.datetime - a.datetime;
|
||||
});
|
||||
|
||||
callback(null, notifs);
|
||||
});
|
||||
};
|
||||
|
||||
function getNotifications(uid, count, callback) {
|
||||
async.parallel({
|
||||
unread: function(next) {
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
<label>Seconds before new user can post</label>
|
||||
<input type="number" class="form-control" value="10" data-field="initialPostDelay">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Number of seconds users are allowed to edit posts after posting. (0 disabled)</label>
|
||||
<input type="number" class="form-control" value="0" data-field="postEditDuration">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Title Length</label>
|
||||
<input type="number" class="form-control" value="3" data-field="minimumTitleLength">
|
||||
|
||||
Reference in New Issue
Block a user