Merge remote-tracking branch 'origin/master' into sortable-menu

v1.18.x
psychobunny 10 years ago
commit b1f835b053

@ -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'));

@ -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);

@ -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">

Loading…
Cancel
Save