diff --git a/package.json b/package.json
index 06cc7fb64e..f237242210 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
},
"dependencies": {
"async": "~1.4.2",
- "bcryptjs": "~2.2.1",
+ "bcryptjs": "~2.3.0",
"body-parser": "^1.9.0",
"colors": "^1.1.0",
"compression": "^1.1.0",
@@ -39,18 +39,18 @@
"mkdirp": "~0.5.0",
"mmmagic": "^0.4.0",
"morgan": "^1.3.2",
- "nconf": "~0.7.1",
- "nodebb-plugin-composer-default": "1.0.16",
+ "nconf": "~0.8.2",
+ "nodebb-plugin-composer-default": "1.0.19",
"nodebb-plugin-dbsearch": "0.2.17",
- "nodebb-plugin-emoji-extended": "0.4.14",
+ "nodebb-plugin-emoji-extended": "0.4.15",
"nodebb-plugin-markdown": "4.0.6",
- "nodebb-plugin-mentions": "1.0.6",
+ "nodebb-plugin-mentions": "1.0.8",
"nodebb-plugin-soundpack-default": "0.1.4",
"nodebb-plugin-spam-be-gone": "0.4.2",
"nodebb-rewards-essentials": "0.0.5",
- "nodebb-theme-lavender": "2.0.6",
- "nodebb-theme-persona": "3.0.45",
- "nodebb-theme-vanilla": "4.0.20",
+ "nodebb-theme-lavender": "2.0.8",
+ "nodebb-theme-persona": "3.0.56",
+ "nodebb-theme-vanilla": "4.0.24",
"nodebb-widget-essentials": "2.0.2",
"npm": "^2.1.4",
"passport": "^0.3.0",
@@ -73,7 +73,7 @@
"underscore.deep": "^0.5.1",
"validator": "^4.0.5",
"winston": "^1.0.1",
- "xregexp": "~2.0.0"
+ "xregexp": "~3.0.0"
},
"devDependencies": {
"mocha": "~1.13.0",
diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json
index 252e893d0d..167ab1aa66 100644
--- a/public/language/en_GB/error.json
+++ b/public/language/en_GB/error.json
@@ -77,7 +77,8 @@
"group-name-too-short": "Group name too short",
"group-already-exists": "Group already exists",
"group-name-change-not-allowed": "Group name change not allowed",
- "group-already-member": "You are already part of this group",
+ "group-already-member": "Already part of this group",
+ "group-not-member": "Not a member of this group",
"group-needs-owner": "This group requires at least one owner",
"group-already-invited": "This user has already been invited",
"group-already-requested": "Your membership request has already been submitted",
diff --git a/public/language/en_GB/modules.json b/public/language/en_GB/modules.json
index e4f509842f..673ad026a9 100644
--- a/public/language/en_GB/modules.json
+++ b/public/language/en_GB/modules.json
@@ -24,6 +24,7 @@
"composer.discard": "Are you sure you wish to discard this post?",
"composer.submit_and_lock": "Submit and Lock",
"composer.toggle_dropdown": "Toggle Dropdown",
+ "composer.uploading": "Uploading %1",
"bootbox.ok": "OK",
"bootbox.cancel": "Cancel",
diff --git a/public/language/en_GB/notifications.json b/public/language/en_GB/notifications.json
index 52d5a10194..7a96e0da53 100644
--- a/public/language/en_GB/notifications.json
+++ b/public/language/en_GB/notifications.json
@@ -14,8 +14,8 @@
"new_message_from": "New message from %1",
"upvoted_your_post_in": "%1 has upvoted your post in %2.",
- "moved_your_post": "%1 has moved your post.",
- "moved_your_topic": "%1 has moved your topic.",
+ "moved_your_post": "%1 has moved your post to %2",
+ "moved_your_topic": "%1 has moved %2",
"favourited_your_post_in": "%1 has favourited your post in %2.",
"user_flagged_post_in": "%1 flagged a post in %2",
"user_posted_to" : "%1 has posted a reply to: %2",
diff --git a/public/language/en_GB/pages.json b/public/language/en_GB/pages.json
index 6d6164ef9e..f398d5b1ff 100644
--- a/public/language/en_GB/pages.json
+++ b/public/language/en_GB/pages.json
@@ -29,6 +29,9 @@
"chat": "Chatting with %1",
"account/edit": "Editing \"%1\"",
+ "account/edit/password": "Editing password of \"%1\"",
+ "account/edit/username": "Editing username of \"%1\"",
+ "account/edit/email": "Editing email of \"%1\"",
"account/following": "People %1 follows",
"account/followers": "People who follow %1",
"account/posts": "Posts made by %1",
diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json
index ab2031be4d..c26d0c05f8 100644
--- a/public/language/en_GB/topic.json
+++ b/public/language/en_GB/topic.json
@@ -32,7 +32,6 @@
"bookmark_instructions" : "Click here to return to the last unread post in this thread.",
"flag_title": "Flag this post for moderation",
- "flag_confirm": "Are you sure you want to flag this post?",
"flag_success": "This post has been flagged for moderation.",
"deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.",
@@ -117,5 +116,10 @@
"most_votes": "Most votes",
"most_posts": "Most posts",
- "stale_topic_warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?"
+ "stale_topic_warning": "The topic you are replying to is quite old. Would you like to create a new topic instead, and reference this one in your reply?",
+
+ "spam": "Spam",
+ "offensive": "Offensive",
+ "custom-flag-reason": "Enter a flagging reason"
+
}
diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json
index 3505d1814c..a4b3f33475 100644
--- a/public/language/en_GB/user.json
+++ b/public/language/en_GB/user.json
@@ -38,6 +38,8 @@
"profile_update_success": "Profile has been updated successfully!",
"change_picture": "Change Picture",
+ "change_username": "Change Username",
+ "change_email": "Change Email",
"edit": "Edit",
"default_picture": "Default Icon",
"uploaded_picture": "Uploaded Picture",
diff --git a/public/less/admin/general/navigation.less b/public/less/admin/general/navigation.less
index 1353d35013..bb3efac15b 100644
--- a/public/less/admin/general/navigation.less
+++ b/public/less/admin/general/navigation.less
@@ -2,6 +2,7 @@
#navigation {
+
#active-navigation {
.active {
background-color: #eee;
@@ -23,6 +24,9 @@
.iconPicker i {
cursor: pointer;
}
+ .form-group {
+ min-height: 80px;
+ }
}
ul {
diff --git a/public/less/admin/manage/groups.less b/public/less/admin/manage/groups.less
index f6dac84921..ceeba06df7 100644
--- a/public/less/admin/manage/groups.less
+++ b/public/less/admin/manage/groups.less
@@ -1,5 +1,20 @@
.group {
- .current_members {
+ [component="groups/members"] {
padding: 0;
+ tbody {
+ max-height: 500px;
+ display: block;
+ overflow-y: auto;
+ padding-bottom: 100px;
+ .member-name {
+ width: 100%;
+ }
+ }
+
+
+ img {
+ width: 32px;
+ height: 32px;
+ }
}
}
\ No newline at end of file
diff --git a/public/less/generics.less b/public/less/generics.less
index ce824ab04f..4115f7d117 100644
--- a/public/less/generics.less
+++ b/public/less/generics.less
@@ -9,7 +9,7 @@
.border-radius(3px);
&.disabled {
- -webkit-filter: grayscale(30%);
+ background-color: #888!important;
.opacity(0.5);
}
}
@@ -54,4 +54,4 @@
height: 100%;
vertical-align: middle;
}
-}
\ No newline at end of file
+}
diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js
index 0309fb48fd..88998bc5f6 100644
--- a/public/src/admin/admin.js
+++ b/public/src/admin/admin.js
@@ -47,7 +47,8 @@
function selectMenuItem(url) {
url = url
.replace(/\/\d+$/, '')
- .split('/').slice(0, 3).join('/');
+ .split('/').slice(0, 3).join('/')
+ .split('?')[0];
// If index is requested, load the dashboard
if (url === 'admin') {
diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js
index 1d346ccd8a..a57fc06701 100644
--- a/public/src/admin/manage/categories.js
+++ b/public/src/admin/manage/categories.js
@@ -1,5 +1,5 @@
"use strict";
-/*global define, socket, app, bootbox, templates, ajaxify, RELATIVE_PATH, Sortable */
+/*global define, socket, app, bootbox, templates, ajaxify, Sortable */
define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-serializeobject.min'], function() {
var Categories = {}, newCategoryId = -1, sortables;
@@ -17,12 +17,17 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
// Enable/Disable toggle events
$('.categories').on('click', 'button[data-action="toggle"]', function() {
- var self = $(this),
- rowEl = self.parents('li'),
- cid = rowEl.attr('data-cid'),
- disabled = rowEl.hasClass('disabled');
+ var $this = $(this),
+ cid = $this.attr('data-cid'),
+ parentEl = $this.parents('li[data-cid="' + cid + '"]'),
+ disabled = parentEl.hasClass('disabled');
- Categories.toggle(cid, disabled);
+ var children = parentEl.find('li[data-cid]').map(function() {
+ return $(this).attr('data-cid');
+ }).get();
+
+ Categories.toggle([cid].concat(children), !disabled);
+ return false;
});
};
@@ -94,14 +99,16 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
}
};
- Categories.toggle = function(cid, disabled) {
+ Categories.toggle = function(cids, disabled) {
var payload = {};
- payload[cid] = {
- disabled: disabled ? 1 : 0
- };
+ cids.forEach(function(cid) {
+ payload[cid] = {
+ disabled: disabled ? 1 : 0
+ };
+ });
- socket.emit('admin.categories.update', payload, function(err, result) {
+ socket.emit('admin.categories.update', payload, function(err) {
if (err) {
return app.alertError(err.message);
}
diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js
index 441afe29d6..0337064ad3 100644
--- a/public/src/admin/manage/category.js
+++ b/public/src/admin/manage/category.js
@@ -240,7 +240,7 @@ define('admin/manage/category', [
}
categories = categories.filter(function(category) {
- return category && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10);
+ return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10);
});
templates.parse('partials/category_list', {
diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js
index 37d2f64295..c708cc3a14 100644
--- a/public/src/admin/manage/group.js
+++ b/public/src/admin/manage/group.js
@@ -1,16 +1,16 @@
"use strict";
-/*global define, templates, socket, ajaxify, app, admin, bootbox, utils, config */
+/*global define, templates, socket, ajaxify, app, bootbox, translator */
define('admin/manage/group', [
+ 'forum/groups/memberlist',
'iconSelect',
'admin/modules/colorpicker'
-], function(iconSelect, colorpicker) {
+], function(memberList, iconSelect, colorpicker) {
var Groups = {};
Groups.init = function() {
var groupDetailsSearch = $('#group-details-search'),
groupDetailsSearchResults = $('#group-details-search-results'),
- groupMembersEl = $('ul.current_members'),
groupIcon = $('#group-icon'),
changeGroupUserTitle = $('#change-group-user-title'),
changeGroupLabelColor = $('#change-group-label-color'),
@@ -20,6 +20,8 @@ define('admin/manage/group', [
var groupName = ajaxify.data.group.name;
+ memberList.init();
+
changeGroupUserTitle.keyup(function() {
groupLabelPreview.text(changeGroupUserTitle.val());
});
@@ -46,10 +48,16 @@ define('admin/manage/group', [
}
groupDetailsSearchResults.empty();
+
for (x = 0; x < numResults; x++) {
foundUser = $('
');
foundUser
- .attr({title: results.users[x].username, 'data-uid': results.users[x].uid})
+ .attr({title: results.users[x].username,
+ 'data-uid': results.users[x].uid,
+ 'data-username': results.users[x].username,
+ 'data-userslug': results.users[x].userslug,
+ 'data-picture': results.users[x].picture
+ })
.append($('
').attr('src', results.users[x].picture))
.append($('').html(results.users[x].username));
@@ -64,45 +72,74 @@ define('admin/manage/group', [
groupDetailsSearchResults.on('click', 'li[data-uid]', function() {
var userLabel = $(this),
- uid = parseInt(userLabel.attr('data-uid'), 10),
- members = [];
+ uid = parseInt(userLabel.attr('data-uid'), 10);
- groupMembersEl.find('li[data-uid]').each(function() {
- members.push(parseInt($(this).attr('data-uid'), 10));
- });
-
- if (members.indexOf(uid) === -1) {
- socket.emit('admin.groups.join', {
- groupName: groupName,
- uid: uid
- }, function(err, data) {
- if (!err) {
- groupMembersEl.append(userLabel.clone(true));
- }
- });
- }
- });
-
- groupMembersEl.on('click', 'li[data-uid]', function() {
- var uid = $(this).attr('data-uid');
-
- bootbox.confirm('Are you sure you want to remove this user?', function(confirm) {
- if (!confirm) {
- return;
+ socket.emit('admin.groups.join', {
+ groupName: groupName,
+ uid: uid
+ }, function(err) {
+ if (err) {
+ return app.alertError(err.message);
}
- socket.emit('admin.groups.leave', {
- groupName: groupName,
- uid: uid
- }, function(err, data) {
- if (err) {
- return app.alertError(err.message);
- }
- groupMembersEl.find('li[data-uid="' + uid + '"]').remove();
+ var member = {
+ uid: userLabel.attr('data-uid'),
+ username: userLabel.attr('data-username'),
+ userslug: userLabel.attr('data-userslug'),
+ picture: userLabel.attr('data-picture')
+ };
+
+ templates.parse('partials/groups/memberlist', 'members', {group: {isOwner: ajaxify.data.group.isOwner, members: [member]}}, function(html) {
+ translator.translate(html, function(html) {
+ $('[component="groups/members"] tr').first().before(html);
+ });
});
});
});
+ $('[component="groups/members"]').on('click', '[data-action]', function() {
+ var btnEl = $(this),
+ userRow = btnEl.parents('[data-uid]'),
+ ownerFlagEl = userRow.find('.member-name i'),
+ isOwner = !ownerFlagEl.hasClass('invisible') ? true : false,
+ uid = userRow.attr('data-uid'),
+ action = btnEl.attr('data-action');
+
+ switch(action) {
+ case 'toggleOwnership':
+ socket.emit('groups.' + (isOwner ? 'rescind' : 'grant'), {
+ toUid: uid,
+ groupName: groupName
+ }, function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ ownerFlagEl.toggleClass('invisible');
+ });
+ break;
+
+ case 'kick':
+ bootbox.confirm('Are you sure you want to remove this user?', function(confirm) {
+ if (!confirm) {
+ return;
+ }
+ socket.emit('admin.groups.leave', {
+ uid: uid,
+ groupName: groupName
+ }, function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ userRow.slideUp().remove();
+ });
+
+ });
+ break;
+ default:
+ break;
+ }
+ });
+
$('#group-icon').on('click', function() {
iconSelect.init(groupIcon);
});
diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js
index d85df5c266..27c5b2459b 100644
--- a/public/src/admin/manage/tags.js
+++ b/public/src/admin/manage/tags.js
@@ -1,5 +1,5 @@
"use strict";
-/*global define, socket, app, admin, utils, bootbox, RELATIVE_PATH*/
+/*global define, socket, app, utils, bootbox*/
define('admin/manage/tags', [
'forum/infinitescroll',
@@ -25,12 +25,12 @@ define('admin/manage/tags', [
}
timeoutId = setTimeout(function() {
- socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, tags) {
+ socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function(err, result) {
if (err) {
return app.alertError(err.message);
}
- infinitescroll.parseAndTranslate('admin/manage/tags', 'tags', {tags: tags}, function(html) {
+ infinitescroll.parseAndTranslate('admin/manage/tags', 'tags', {tags: result.tags}, function(html) {
$('.tag-list').html(html);
utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
timeoutId = 0;
@@ -43,7 +43,7 @@ define('admin/manage/tags', [
}
function handleModify() {
- $('#modify').on('click', function(ev) {
+ $('#modify').on('click', function() {
var tagsToModify = $('.tag-row.selected');
if (!tagsToModify.length) {
return;
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 2f11307471..19c0abe8fd 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -46,7 +46,7 @@ $(document).ready(function() {
return true;
}
- app.enterRoom('');
+ app.leaveCurrentRoom();
$(window).off('scroll');
@@ -56,6 +56,7 @@ $(document).ready(function() {
url = ajaxify.start(url, quiet);
+ $('body').removeClass(ajaxify.data.bodyClass);
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
ajaxify.loadData(url, function(err, data) {
@@ -141,6 +142,7 @@ $(document).ready(function() {
templates.parse(tpl_url, data, function(template) {
translator.translate(template, function(translatedTemplate) {
+ $('body').addClass(data.bodyClass);
$('#content').html(translatedTemplate);
ajaxify.end(url, tpl_url);
@@ -222,9 +224,7 @@ $(document).ready(function() {
$(window).trigger('action:ajaxify.dataLoaded', {url: url, data: data});
- if (callback) {
- callback(null, data);
- }
+ callback(null, data);
},
error: function(data, textStatus) {
if (data.status === 0 && textStatus === 'error') {
diff --git a/public/src/app.js b/public/src/app.js
index e2265fbe24..f739ac9c4f 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -71,12 +71,14 @@ app.cacheBuster = null;
}
});
- require(['taskbar', 'helpers'], function(taskbar, helpers) {
+ require(['taskbar', 'helpers', 'forum/pagination'], function(taskbar, helpers, pagination) {
taskbar.init();
// templates.js helpers
helpers.register();
+ pagination.init();
+
$(window).trigger('action:app.load');
});
});
@@ -143,14 +145,25 @@ app.cacheBuster = null;
'icon:text': app.user['icon:text']
}, function(err) {
if (err) {
- app.alertError(err.message);
- return;
+ return app.alertError(err.message);
}
app.currentRoom = room;
});
}
};
+ app.leaveCurrentRoom = function() {
+ if (!socket) {
+ return;
+ }
+ socket.emit('meta.rooms.leaveCurrent', function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.currentRoom = '';
+ });
+ }
+
function highlightNavigationLink() {
var path = window.location.pathname;
$('#main-nav li').removeClass('active');
diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js
index 7271e854bc..7969a1bcf1 100644
--- a/public/src/client/account/edit.js
+++ b/public/src/client/account/edit.js
@@ -1,12 +1,11 @@
'use strict';
-/* globals define, ajaxify, socket, app, config, utils, bootbox */
+/* globals define, ajaxify, socket, app, config, templates, bootbox */
define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function(header, uploader, translator) {
var AccountEdit = {},
uploadedPicture = '',
- selectedImageType = '',
- currentEmail;
+ selectedImageType = '';
AccountEdit.init = function() {
uploadedPicture = ajaxify.data.uploadedpicture;
@@ -23,12 +22,9 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
});
});
- currentEmail = $('#inputEmail').val();
-
handleImageChange();
handleAccountDelete();
handleEmailConfirm();
- handlePasswordChange();
updateSignature();
updateAboutMe();
};
@@ -36,8 +32,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
function updateProfile() {
var userData = {
uid: $('#inputUID').val(),
- username: $('#inputUsername').val(),
- email: $('#inputEmail').val(),
fullname: $('#inputFullname').val(),
website: $('#inputWebsite').val(),
birthday: $('#inputBirthday').val(),
@@ -57,27 +51,13 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
$('#user-current-picture').attr('src', data.picture);
}
- if (data.userslug) {
- var oldslug = $('.account-username-box').attr('data-userslug');
- $('.account-username-box a').each(function() {
- $(this).attr('href', $(this).attr('href').replace(oldslug, data.userslug));
- });
-
- $('.account-username-box').attr('data-userslug', data.userslug);
- }
-
- if (currentEmail !== data.email) {
- currentEmail = data.email;
- $('#confirm-email').removeClass('hide');
- }
-
- updateHeader(data.picture, userData.username, data.userslug);
+ updateHeader(data.picture);
});
return false;
}
- function updateHeader(picture, username, userslug) {
+ function updateHeader(picture) {
require(['components'], function(components) {
if (parseInt(ajaxify.data.theirid, 10) !== parseInt(ajaxify.data.yourid, 10)) {
return;
@@ -88,11 +68,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
if (picture) {
components.get('header/userpicture').attr('src', picture);
}
-
- if (username && userslug) {
- components.get('header/profilelink').attr('href', config.relative_path + '/user/' + userslug);
- components.get('header/username').text(username);
- }
});
}
@@ -273,88 +248,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
});
}
- function handlePasswordChange() {
- var currentPassword = $('#inputCurrentPassword');
- var password_notify = $('#password-notify');
- var password_confirm_notify = $('#password-confirm-notify');
- var password = $('#inputNewPassword');
- var password_confirm = $('#inputNewPasswordAgain');
- var passwordvalid = false;
- var passwordsmatch = false;
-
- function onPasswordChanged() {
- if (password.val().length < config.minimumPasswordLength) {
- showError(password_notify, '[[user:change_password_error_length]]');
- passwordvalid = false;
- } else if (!utils.isPasswordValid(password.val())) {
- showError(password_notify, '[[user:change_password_error]]');
- passwordvalid = false;
- } else {
- showSuccess(password_notify);
- passwordvalid = true;
- }
- }
-
- function onPasswordConfirmChanged() {
- if (password.val() !== password_confirm.val()) {
- showError(password_confirm_notify, '[[user:change_password_error_match]]');
- passwordsmatch = false;
- } else {
- if (password.val()) {
- showSuccess(password_confirm_notify);
- } else {
- password_confirm_notify.parent().removeClass('alert-success alert-danger');
- password_confirm_notify.children().show();
- password_confirm_notify.find('.msg').html('');
- }
-
- passwordsmatch = true;
- }
- }
-
- password.on('blur', onPasswordChanged);
- password_confirm.on('blur', onPasswordConfirmChanged);
-
- $('#changePasswordBtn').on('click', function() {
- onPasswordChanged();
- onPasswordConfirmChanged();
-
- var btn = $(this);
- if ((passwordvalid && passwordsmatch) || app.user.isAdmin) {
- btn.addClass('disabled').find('i').removeClass('hide');
- socket.emit('user.changePassword', {
- 'currentPassword': currentPassword.val(),
- 'newPassword': password.val(),
- 'uid': ajaxify.data.theirid
- }, function(err) {
- btn.removeClass('disabled').find('i').addClass('hide');
- currentPassword.val('');
- password.val('');
- password_confirm.val('');
- passwordsmatch = false;
- passwordvalid = false;
-
- if (err) {
- onPasswordChanged();
- onPasswordConfirmChanged();
- return app.alertError(err.message);
- }
-
- app.alertSuccess('[[user:change_password_success]]');
- });
- } else {
- if (!passwordsmatch) {
- app.alertError('[[user:change_password_error_match]]');
- }
-
- if (!passwordvalid) {
- app.alertError('[[user:change_password_error]]');
- }
- }
- return false;
- });
- }
-
function changeUserPicture(type, callback) {
socket.emit('user.changePicture', {
type: type,
@@ -384,24 +277,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
});
}
- function showError(element, msg) {
- translator.translate(msg, function(msg) {
- element.find('.error').html(msg).removeClass('hide').siblings().addClass('hide');
-
- element.parent()
- .removeClass('alert-success')
- .addClass('alert-danger');
- element.show();
- });
- }
-
- function showSuccess(element) {
- element.find('.success').removeClass('hide').siblings().addClass('hide');
- element.parent()
- .removeClass('alert-danger')
- .addClass('alert-success');
- element.show();
- }
return AccountEdit;
-});
\ No newline at end of file
+});
diff --git a/public/src/client/account/edit/email.js b/public/src/client/account/edit/email.js
new file mode 100644
index 0000000000..4014039740
--- /dev/null
+++ b/public/src/client/account/edit/email.js
@@ -0,0 +1,39 @@
+'use strict';
+
+/* globals define, ajaxify, socket, app */
+
+define('forum/account/edit/email', ['forum/account/header'], function(header) {
+ var AccountEditEmail = {};
+
+ AccountEditEmail.init = function() {
+ header.init();
+
+ $('#submitBtn').on('click', function () {
+ var userData = {
+ uid: $('#inputUID').val(),
+ email: $('#inputNewEmail').val(),
+ password: $('#inputCurrentPassword').val()
+ };
+
+ if (!userData.email) {
+ return;
+ }
+
+ var btn = $(this);
+ btn.addClass('disabled').find('i').removeClass('hide');
+
+ socket.emit('user.changeUsernameEmail', userData, function(err) {
+ btn.removeClass('disabled').find('i').addClass('hide');
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ ajaxify.go('user/' + ajaxify.data.userslug);
+ });
+
+ return false;
+ });
+ };
+
+ return AccountEditEmail;
+});
diff --git a/public/src/client/account/edit/password.js b/public/src/client/account/edit/password.js
new file mode 100644
index 0000000000..3e67045925
--- /dev/null
+++ b/public/src/client/account/edit/password.js
@@ -0,0 +1,116 @@
+'use strict';
+
+/* globals define, ajaxify, socket, app, config, utils */
+
+define('forum/account/edit/password', ['forum/account/header', 'translator'], function(header, translator) {
+ var AccountEditPassword = {};
+
+ AccountEditPassword.init = function() {
+ header.init();
+
+ handlePasswordChange();
+ };
+
+ function handlePasswordChange() {
+ var currentPassword = $('#inputCurrentPassword');
+ var password_notify = $('#password-notify');
+ var password_confirm_notify = $('#password-confirm-notify');
+ var password = $('#inputNewPassword');
+ var password_confirm = $('#inputNewPasswordAgain');
+ var passwordvalid = false;
+ var passwordsmatch = false;
+
+ function onPasswordChanged() {
+ if (password.val().length < config.minimumPasswordLength) {
+ showError(password_notify, '[[user:change_password_error_length]]');
+ passwordvalid = false;
+ } else if (!utils.isPasswordValid(password.val())) {
+ showError(password_notify, '[[user:change_password_error]]');
+ passwordvalid = false;
+ } else {
+ showSuccess(password_notify);
+ passwordvalid = true;
+ }
+ }
+
+ function onPasswordConfirmChanged() {
+ if (password.val() !== password_confirm.val()) {
+ showError(password_confirm_notify, '[[user:change_password_error_match]]');
+ passwordsmatch = false;
+ } else {
+ if (password.val()) {
+ showSuccess(password_confirm_notify);
+ } else {
+ password_confirm_notify.parent().removeClass('alert-success alert-danger');
+ password_confirm_notify.children().show();
+ password_confirm_notify.find('.msg').html('');
+ }
+
+ passwordsmatch = true;
+ }
+ }
+
+ password.on('blur', onPasswordChanged);
+ password_confirm.on('blur', onPasswordConfirmChanged);
+
+ $('#changePasswordBtn').on('click', function() {
+ onPasswordChanged();
+ onPasswordConfirmChanged();
+
+ var btn = $(this);
+ if ((passwordvalid && passwordsmatch) || app.user.isAdmin) {
+ btn.addClass('disabled').find('i').removeClass('hide');
+ socket.emit('user.changePassword', {
+ 'currentPassword': currentPassword.val(),
+ 'newPassword': password.val(),
+ 'uid': ajaxify.data.theirid
+ }, function(err) {
+ btn.removeClass('disabled').find('i').addClass('hide');
+ currentPassword.val('');
+ password.val('');
+ password_confirm.val('');
+ passwordsmatch = false;
+ passwordvalid = false;
+
+ if (err) {
+ onPasswordChanged();
+ onPasswordConfirmChanged();
+ return app.alertError(err.message);
+ }
+ ajaxify.go('user/' + ajaxify.data.userslug);
+ app.alertSuccess('[[user:change_password_success]]');
+ });
+ } else {
+ if (!passwordsmatch) {
+ app.alertError('[[user:change_password_error_match]]');
+ }
+
+ if (!passwordvalid) {
+ app.alertError('[[user:change_password_error]]');
+ }
+ }
+ return false;
+ });
+ }
+
+ function showError(element, msg) {
+ translator.translate(msg, function(msg) {
+ element.find('.error').html(msg).removeClass('hide').siblings().addClass('hide');
+
+ element.parent()
+ .removeClass('alert-success')
+ .addClass('alert-danger');
+ element.show();
+ });
+ }
+
+ function showSuccess(element) {
+ element.find('.success').removeClass('hide').siblings().addClass('hide');
+ element.parent()
+ .removeClass('alert-danger')
+ .addClass('alert-success');
+ element.show();
+ }
+
+ return AccountEditPassword;
+});
diff --git a/public/src/client/account/edit/username.js b/public/src/client/account/edit/username.js
new file mode 100644
index 0000000000..4fcddf81f5
--- /dev/null
+++ b/public/src/client/account/edit/username.js
@@ -0,0 +1,43 @@
+'use strict';
+
+/* globals define, ajaxify, socket, app, utils, config */
+
+define('forum/account/edit/username', ['forum/account/header'], function(header) {
+ var AccountEditUsername = {};
+
+ AccountEditUsername.init = function() {
+ header.init();
+
+ $('#submitBtn').on('click', function updateUsername() {
+ var userData = {
+ uid: $('#inputUID').val(),
+ username: $('#inputNewUsername').val(),
+ password: $('#inputCurrentPassword').val()
+ };
+
+ if (!userData.username) {
+ return;
+ }
+ var btn = $(this);
+ btn.addClass('disabled').find('i').removeClass('hide');
+ socket.emit('user.changeUsernameEmail', userData, function(err) {
+ btn.removeClass('disabled').find('i').addClass('hide');
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ var userslug = utils.slugify(userData.username);
+ if (userData.username && userslug && parseInt(userData.uid, 10) === parseInt(app.user.uid, 10)) {
+ $('[component="header/profilelink"]').attr('href', config.relative_path + '/user/' + userslug);
+ $('[component="header/username"]').text(userData.username);
+ }
+
+ ajaxify.go('user/' + userslug);
+ });
+
+ return false;
+ });
+ };
+
+ return AccountEditUsername;
+});
diff --git a/public/src/client/category.js b/public/src/client/category.js
index 7a295c1b28..634317a21c 100644
--- a/public/src/client/category.js
+++ b/public/src/client/category.js
@@ -2,7 +2,6 @@
/* global define, config, templates, app, utils, ajaxify, socket */
define('forum/category', [
- 'forum/pagination',
'forum/infinitescroll',
'share',
'navigator',
@@ -10,13 +9,12 @@ define('forum/category', [
'sort',
'components',
'translator'
-
-], function(pagination, infinitescroll, share, navigator, categoryTools, sort, components, translator) {
+], function(infinitescroll, share, navigator, categoryTools, sort, components, translator) {
var Category = {};
$(window).on('action:ajaxify.start', function(ev, data) {
if (ajaxify.currentPage !== data.url) {
- navigator.hide();
+ navigator.disable();
removeListeners();
}
@@ -41,12 +39,12 @@ define('forum/category', [
sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug);
- enableInfiniteLoadingOrPagination();
-
if (!config.usePagination) {
navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback);
}
+ enableInfiniteLoadingOrPagination();
+
$('[component="category"]').on('click', '[component="topic/header"]', function() {
var clickedIndex = $(this).parents('[data-index]').attr('data-index');
$('[component="category/topic"]').each(function(index, el) {
@@ -112,7 +110,7 @@ define('forum/category', [
if (config.usePagination) {
var page = Math.ceil((parseInt(bookmarkIndex, 10) + 1) / config.topicsPerPage);
- if (parseInt(page, 10) !== pagination.currentPage) {
+ if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) {
pagination.loadPage(page, function() {
Category.scrollToTopic(bookmarkIndex, clickedIndex, 400);
});
@@ -175,8 +173,7 @@ define('forum/category', [
if (!config.usePagination) {
infinitescroll.init($('[component="category"]'), Category.loadMoreTopics);
} else {
- navigator.hide();
- pagination.init(ajaxify.data.currentPage, ajaxify.data.pageCount);
+ navigator.disable();
}
}
diff --git a/public/src/client/chats.js b/public/src/client/chats.js
index 511a81ca15..3d7bd7bda0 100644
--- a/public/src/client/chats.js
+++ b/public/src/client/chats.js
@@ -140,7 +140,7 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
Chats.switchChat = function(uid, username) {
if (!$('[component="chat/messages"]').length) {
- ajaxify.go('chats/' + username);
+ return ajaxify.go('chats/' + utils.slugify(username));
}
var contactEl = $('.chats-list [data-uid="' + uid + '"]');
diff --git a/public/src/client/footer.js b/public/src/client/footer.js
index 5e39ac142c..288123c8e1 100644
--- a/public/src/client/footer.js
+++ b/public/src/client/footer.js
@@ -7,21 +7,13 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu
Chat.prepareDOM();
translator.prepareDOM();
- function updateUnreadTopicCount(err, count) {
- if (err) {
- return console.warn('Error updating unread count', err);
- }
-
+ function updateUnreadTopicCount(count) {
$('#unread-count i')
.toggleClass('unread-count', count > 0)
.attr('data-content', count > 20 ? '20+' : count);
}
- function updateUnreadChatCount(err, count) {
- if (err) {
- return console.warn('Error updating unread count', err);
- }
-
+ function updateUnreadChatCount(count) {
components.get('chat/icon')
.toggleClass('unread-count', count > 0)
.attr('data-content', count > 20 ? '20+' : count);
@@ -62,11 +54,20 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu
socket.on('event:new_post', onNewPost);
}
- socket.on('event:unread.updateCount', updateUnreadTopicCount);
- socket.emit('user.getUnreadCount', updateUnreadTopicCount);
+ if (app.user.uid) {
+ socket.emit('user.getUnreadCounts', function(err, data) {
+ if (err) {
+ return app.alert(err.message);
+ }
+ updateUnreadTopicCount(data.unreadTopicCount);
+ updateUnreadChatCount(data.unreadChatCount);
+ Notifications.updateNotifCount(data.unreadNotificationCount);
+ });
+ }
+
+ socket.on('event:unread.updateCount', updateUnreadTopicCount);
socket.on('event:unread.updateChatCount', updateUnreadChatCount);
- socket.emit('user.getUnreadChatCount', updateUnreadChatCount);
initUnreadTopics();
});
diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js
index f5fb69e401..be624b2160 100644
--- a/public/src/client/groups/details.js
+++ b/public/src/client/groups/details.js
@@ -1,17 +1,22 @@
"use strict";
-/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */
+/* globals define, socket, ajaxify, app, bootbox, utils */
+
+define('forum/groups/details', [
+ 'forum/groups/memberlist',
+ 'iconSelect',
+ 'components',
+ 'vendor/colorpicker/colorpicker',
+ 'vendor/jquery/draggable-background/backgroundDraggable'
+], function(memberList, iconSelect, components) {
-define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescroll', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components, infinitescroll) {
var Details = {
cover: {}
};
- var searchInterval;
var groupName;
Details.init = function() {
- var detailsPage = components.get('groups/container'),
- settingsFormEl = detailsPage.find('form');
+ var detailsPage = components.get('groups/container');
groupName = ajaxify.data.group.name;
@@ -20,8 +25,8 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol
Details.initialiseCover();
}
- handleMemberSearch();
- handleMemberInfiniteScroll();
+ memberList.init();
+
handleMemberInvitations();
components.get('groups/activity').find('.content img:not(.not-responsive)').addClass('img-responsive');
@@ -291,44 +296,6 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol
});
};
- function handleMemberSearch() {
- $('[component="groups/members/search"]').on('keyup', function() {
- var query = $(this).val();
- if (searchInterval) {
- clearInterval(searchInterval);
- searchInterval = 0;
- }
-
- searchInterval = setTimeout(function() {
- socket.emit('groups.searchMembers', {groupName: groupName, query: query}, function(err, results) {
- if (err) {
- return app.alertError(err.message);
- }
-
- infinitescroll.parseAndTranslate('groups/details', 'members', {
- group: {
- members: results.users,
- isOwner: ajaxify.data.group.isOwner
- }
- }, function(html) {
- $('[component="groups/members"] tbody').html(html);
- $('[component="groups/members"]').attr('data-nextstart', 20);
- });
- });
- }, 250);
- });
- }
-
- function handleMemberInfiniteScroll() {
- $('[component="groups/members"] tbody').on('scroll', function() {
- var $this = $(this);
- var bottom = ($this[0].scrollHeight - $this.height()) * 0.9;
- if ($this.scrollTop() > bottom) {
- loadMoreMembers();
- }
- });
- }
-
function handleMemberInvitations() {
if (ajaxify.data.group.isOwner) {
var searchInput = $('[component="groups/members/invite"]');
@@ -349,48 +316,5 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol
}
}
- function loadMoreMembers() {
-
- var members = $('[component="groups/members"]');
- if (members.attr('loading')) {
- return;
- }
-
- members.attr('loading', 1);
- socket.emit('groups.loadMoreMembers', {
- groupName: groupName,
- after: members.attr('data-nextstart')
- }, function(err, data) {
- if (err) {
- return app.alertError(err.message);
- }
-
- if (data && data.users.length) {
- onMembersLoaded(data.users, function() {
- members.removeAttr('loading');
- members.attr('data-nextstart', data.nextStart);
- });
- } else {
- members.removeAttr('loading');
- }
- });
- }
-
- function onMembersLoaded(users, callback) {
- users = users.filter(function(user) {
- return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length;
- });
-
- infinitescroll.parseAndTranslate('groups/details', 'members', {
- group: {
- members: users,
- isOwner: ajaxify.data.group.isOwner
- }
- }, function(html) {
- $('[component="groups/members"] tbody').append(html);
- callback();
- });
- }
-
return Details;
});
\ No newline at end of file
diff --git a/public/src/client/groups/memberlist.js b/public/src/client/groups/memberlist.js
new file mode 100644
index 0000000000..0cbc0e9116
--- /dev/null
+++ b/public/src/client/groups/memberlist.js
@@ -0,0 +1,97 @@
+"use strict";
+/* globals define, socket, ajaxify, app */
+
+define('forum/groups/memberlist', ['components', 'forum/infinitescroll'], function(components, infinitescroll) {
+
+ var MemberList = {};
+ var searchInterval;
+ var groupName;
+
+ MemberList.init = function() {
+ groupName = ajaxify.data.group.name;
+
+ handleMemberSearch();
+ handleMemberInfiniteScroll();
+ };
+
+ function handleMemberSearch() {
+ $('[component="groups/members/search"]').on('keyup', function() {
+ var query = $(this).val();
+ if (searchInterval) {
+ clearInterval(searchInterval);
+ searchInterval = 0;
+ }
+
+ searchInterval = setTimeout(function() {
+ socket.emit('groups.searchMembers', {groupName: groupName, query: query}, function(err, results) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ parseAndTranslate(results.users, function(html) {
+ $('[component="groups/members"] tbody').html(html);
+ $('[component="groups/members"]').attr('data-nextstart', 20);
+ });
+ });
+ }, 250);
+ });
+ }
+
+ function handleMemberInfiniteScroll() {
+ $('[component="groups/members"] tbody').on('scroll', function() {
+ var $this = $(this);
+ var bottom = ($this[0].scrollHeight - $this.innerHeight()) * 0.9;
+
+ if ($this.scrollTop() > bottom) {
+ loadMoreMembers();
+ }
+ });
+ }
+
+ function loadMoreMembers() {
+ var members = $('[component="groups/members"]');
+ if (members.attr('loading')) {
+ return;
+ }
+
+ members.attr('loading', 1);
+ socket.emit('groups.loadMoreMembers', {
+ groupName: groupName,
+ after: members.attr('data-nextstart')
+ }, function(err, data) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ if (data && data.users.length) {
+ onMembersLoaded(data.users, function() {
+ members.removeAttr('loading');
+ members.attr('data-nextstart', data.nextStart);
+ });
+ } else {
+ members.removeAttr('loading');
+ }
+ });
+ }
+
+ function onMembersLoaded(users, callback) {
+ users = users.filter(function(user) {
+ return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length;
+ });
+
+ parseAndTranslate(users, function(html) {
+ $('[component="groups/members"] tbody').append(html);
+ callback();
+ });
+ }
+
+ function parseAndTranslate(users, callback) {
+ infinitescroll.parseAndTranslate('groups/details', 'members', {
+ group: {
+ members: users,
+ isOwner: ajaxify.data.group.isOwner
+ }
+ }, callback);
+ }
+
+ return MemberList;
+});
\ No newline at end of file
diff --git a/public/src/client/notifications.js b/public/src/client/notifications.js
index 4f2dfbf197..7c0dcd48c3 100644
--- a/public/src/client/notifications.js
+++ b/public/src/client/notifications.js
@@ -2,12 +2,12 @@
/* globals define, socket, app */
-define('forum/notifications', ['components', 'notifications'], function(components, notifs) {
+define('forum/notifications', ['components', 'notifications', 'forum/infinitescroll'], function(components, notifs, infinitescroll) {
var Notifications = {};
Notifications.init = function() {
var listEl = $('.notifications-list');
- listEl.on('click', '[component="notifications/item/link"]', function(e) {
+ listEl.on('click', '[component="notifications/item/link"]', function() {
var nid = $(this).parents('[data-nid]').attr('data-nid');
socket.emit('notifications.markRead', nid, function(err) {
if (err) {
@@ -28,7 +28,32 @@ define('forum/notifications', ['components', 'notifications'], function(componen
notifs.updateNotifCount(0);
});
});
+
+ infinitescroll.init(loadMoreNotifications);
};
+ function loadMoreNotifications(direction) {
+ if (direction < 0) {
+ return;
+ }
+ var notifList = $('.notifications-list');
+ infinitescroll.loadMore('notifications.loadMore', {
+ after: notifList.attr('data-nextstart')
+ }, function(data, done) {
+ if (!data) {
+ return done();
+ }
+ notifList.attr('data-nextstart', data.nextStart);
+ if (!data.notifications || !data.notifications.length) {
+ return done();
+ }
+ infinitescroll.parseAndTranslate('notifications', 'notifications', {notifications: data.notifications}, function(html) {
+ notifList.append(html);
+ html.find('.timeago').timeago();
+ done();
+ });
+ });
+ }
+
return Notifications;
});
diff --git a/public/src/client/pagination.js b/public/src/client/pagination.js
index a94967c7c5..534b4e27f2 100644
--- a/public/src/client/pagination.js
+++ b/public/src/client/pagination.js
@@ -4,14 +4,8 @@
define('forum/pagination', function() {
var pagination = {};
- pagination.currentPage = 0;
- pagination.pageCount = 0;
-
- pagination.init = function(currentPage, pageCount) {
- pagination.currentPage = parseInt(currentPage, 10);
- pagination.pageCount = parseInt(pageCount, 10);
-
- $('.pagination').on('click', '.select-page', function(e) {
+ pagination.init = function() {
+ $('body').on('click', '.pagination .select-page', function(e) {
e.preventDefault();
bootbox.prompt('Enter page number:', function(pageNum) {
pagination.loadPage(pageNum);
@@ -22,10 +16,14 @@ define('forum/pagination', function() {
pagination.loadPage = function(page, callback) {
callback = callback || function() {};
page = parseInt(page, 10);
- if (!utils.isNumber(page) || page < 1 || page > pagination.pageCount) {
+ if (!utils.isNumber(page) || page < 1 || page > ajaxify.data.pagination.pageCount) {
return;
}
- var url = window.location.pathname.slice(1).split('/').slice(0, 3).join('/') + '?page=' + page;
+
+ var query = utils.params();
+ query.page = page;
+
+ var url = window.location.pathname + '?' + $.param(query);
ajaxify.go(url, callback);
};
diff --git a/public/src/client/tags.js b/public/src/client/tags.js
index 4763d3aa44..dedeb4a746 100644
--- a/public/src/client/tags.js
+++ b/public/src/client/tags.js
@@ -24,7 +24,7 @@ define('forum/tags', ['forum/infinitescroll'], function(infinitescroll) {
if (err) {
return app.alertError(err.message);
}
- onTagsLoaded(results, true, function() {
+ onTagsLoaded(results.tags, true, function() {
timeoutId = 0;
});
});
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index 89b6e30a1d..faaaddbf00 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -4,7 +4,6 @@
/* globals define, app, socket, config, ajaxify, RELATIVE_PATH, utils */
define('forum/topic', [
- 'forum/pagination',
'forum/infinitescroll',
'forum/topic/threadTools',
'forum/topic/postTools',
@@ -14,13 +13,13 @@ define('forum/topic', [
'navigator',
'sort',
'components'
-], function(pagination, infinitescroll, threadTools, postTools, events, browsing, posts, navigator, sort, components) {
+], function(infinitescroll, threadTools, postTools, events, browsing, posts, navigator, sort, components) {
var Topic = {},
currentUrl = '';
$(window).on('action:ajaxify.start', function(ev, data) {
if (ajaxify.currentPage !== data.url) {
- navigator.hide();
+ navigator.disable();
components.get('navbar/title').find('span').text('').hide();
app.removeAlert('bookmark');
@@ -147,7 +146,7 @@ define('forum/topic', [
if (components.get('post/anchor', postIndex).length) {
return navigator.scrollToPostIndex(postIndex, true);
}
- } else if (bookmark && (!config.usePagination || (config.usePagination && pagination.currentPage === 1)) && ajaxify.data.postcount > 10) {
+ } else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > 5) {
app.alert({
alert_id: 'bookmark',
message: '[[topic:bookmark_instructions]]',
@@ -217,13 +216,10 @@ define('forum/topic', [
if (!config.usePagination) {
infinitescroll.init($('[component="topic"]'), posts.loadMorePosts);
} else {
- navigator.hide();
-
- pagination.init(parseInt(ajaxify.data.currentPage, 10), parseInt(ajaxify.data.pageCount, 10));
+ navigator.disable();
}
}
-
function updateTopicTitle() {
if ($(window).scrollTop() > 50) {
components.get('navbar/title').find('span').text(ajaxify.data.title).show();
@@ -281,7 +277,7 @@ define('forum/topic', [
var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark';
var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey);
- if (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10)) {
+ if (ajaxify.data.postcount > 5 && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) {
if (app.user.uid) {
socket.emit('topics.bookmark', {
'tid': ajaxify.data.tid,
diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js
index fe9196e7fb..47c7f2c328 100644
--- a/public/src/client/topic/events.js
+++ b/public/src/client/topic/events.js
@@ -162,6 +162,7 @@ define('forum/topic/events', [
function onPostPurged(pid) {
components.get('post', 'pid', pid).fadeOut(500, function() {
$(this).remove();
+ posts.showBottomPostBar();
});
postTools.updatePostCount();
diff --git a/public/src/client/topic/flag.js b/public/src/client/topic/flag.js
new file mode 100644
index 0000000000..3101c0cd1c
--- /dev/null
+++ b/public/src/client/topic/flag.js
@@ -0,0 +1,64 @@
+'use strict';
+
+/* globals define, app, socket, templates, translator */
+
+define('forum/topic/flag', [], function() {
+
+ var Flag = {},
+ flagModal,
+ flagCommit;
+
+ Flag.showFlagModal = function(pid) {
+ parseModal(function(html) {
+ flagModal = $(html);
+
+ flagModal.on('hidden.bs.modal', function() {
+ flagModal.remove();
+ });
+
+ flagCommit = flagModal.find('#flag-post-commit');
+
+ flagModal.on('click', '.flag-reason', function() {
+ flagPost(pid, $(this).text());
+ });
+
+ flagCommit.on('click', function() {
+ flagPost(pid, flagModal.find('#flag-reason-custom').val());
+ });
+
+ flagModal.modal('show');
+
+ flagModal.find('#flag-reason-custom').on('keyup blur change', checkFlagButtonEnable);
+ });
+ };
+
+ function parseModal(callback) {
+ templates.parse('partials/modals/flag_post_modal', {}, function(html) {
+ translator.translate(html, callback);
+ });
+ }
+
+ function flagPost(pid, reason) {
+ if (!pid || !reason) {
+ return;
+ }
+ socket.emit('posts.flag', {pid: pid, reason: reason}, function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ flagModal.modal('hide');
+ app.alertSuccess('[[topic:flag_success]]');
+ });
+ }
+
+ function checkFlagButtonEnable() {
+ if (flagModal.find('#flag-reason-custom').val()) {
+ flagCommit.removeAttr('disabled');
+ } else {
+ flagCommit.attr('disabled', true);
+ }
+ }
+
+ return Flag;
+});
diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js
index 002849f167..764ec7cd0a 100644
--- a/public/src/client/topic/postTools.js
+++ b/public/src/client/topic/postTools.js
@@ -1,6 +1,6 @@
'use strict';
-/* globals define, app, ajaxify, bootbox, socket, templates, utils */
+/* globals define, app, ajaxify, bootbox, socket, templates, utils, config */
define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator'], function(share, navigator, components, translator) {
@@ -15,6 +15,8 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
share.addShareHandlers(topicName);
addVoteHandler();
+
+ PostTools.updatePostCount(ajaxify.data.postcount);
};
PostTools.toggle = function(pid, isDeleted) {
@@ -28,20 +30,16 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
postEl.find('[component="post/purge"]').toggleClass('hidden', !isDeleted);
};
- PostTools.updatePostCount = function() {
- socket.emit('topics.postcount', ajaxify.data.tid, function(err, postCount) {
- if (!err) {
- var postCountEl = components.get('topic/post-count');
- postCountEl.html(postCount).attr('title', postCount);
- utils.makeNumbersHumanReadable(postCountEl);
- navigator.setCount(postCount);
- }
- });
+ PostTools.updatePostCount = function(postCount) {
+ var postCountEl = components.get('topic/post-count');
+ postCountEl.html(postCount).attr('title', postCount);
+ utils.makeNumbersHumanReadable(postCountEl);
+ navigator.setCount(postCount);
};
function addVoteHandler() {
- components.get('topic').on('mouseenter', '[data-pid] .votes', function() {
- loadDataAndCreateTooltip($(this));
+ components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', function() {
+ loadDataAndCreateTooltip($(this).parent());
});
}
@@ -55,6 +53,13 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
}
function createTooltip(el, data) {
+ function doCreateTooltip(title) {
+ el.attr('title', title).tooltip('fixTitle').tooltip('show');
+ el.on('hidden.bs.tooltip', function() {
+ el.tooltip('destroy');
+ el.off('hidden.bs.tooltip');
+ });
+ }
var usernames = data.usernames;
if (!usernames.length) {
return;
@@ -63,11 +68,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
usernames = usernames.join(', ').replace(/,/g, '|');
translator.translate('[[topic:users_and_others, ' + usernames + ', ' + data.otherCount + ']]', function(translated) {
translated = translated.replace(/\|/g, ',');
- el.attr('title', translated).tooltip('destroy').tooltip('show');
+ doCreateTooltip(translated);
});
} else {
usernames = usernames.join(', ');
- el.attr('title', usernames).tooltip('destroy').tooltip('show');
+ doCreateTooltip(usernames);
}
}
@@ -103,40 +108,42 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
});
postContainer.on('click', '[component="post/flag"]', function() {
- flagPost(getData($(this), 'data-pid'));
+ var pid = getData($(this), 'data-pid');
+ require(['forum/topic/flag'], function(flag) {
+ flag.showFlagModal(pid);
+ });
});
- postContainer.on('click', '[component="post/edit"]', function(e) {
+ postContainer.on('click', '[component="post/edit"]', function() {
var btn = $(this);
$(window).trigger('action:composer.post.edit', {
pid: getData(btn, 'data-pid')
});
});
- postContainer.on('click', '[component="post/delete"]', function(e) {
+ postContainer.on('click', '[component="post/delete"]', function() {
togglePostDelete($(this), tid);
});
- postContainer.on('click', '[component="post/restore"]', function(e) {
+ postContainer.on('click', '[component="post/restore"]', function() {
togglePostDelete($(this), tid);
});
- postContainer.on('click', '[component="post/purge"]', function(e) {
+ postContainer.on('click', '[component="post/purge"]', function() {
purgePost($(this), tid);
});
- postContainer.on('click', '[component="post/move"]', function(e) {
+ postContainer.on('click', '[component="post/move"]', function() {
openMovePostModal($(this));
});
- postContainer.on('click', '[component="post/chat"]', function(e) {
+ postContainer.on('click', '[component="post/chat"]', function() {
openChat($(this));
});
}
function onReplyClicked(button, tid, topicName) {
showStaleWarning(function(proceed) {
- console.log('proceed is', proceed);
if (!proceed) {
var selectionText = '',
selection = window.getSelection ? window.getSelection() : document.selection.createRange();
@@ -363,22 +370,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator
});
}
- function flagPost(pid) {
- translator.translate('[[topic:flag_confirm]]', function(message) {
- bootbox.confirm(message, function(confirm) {
- if (!confirm) {
- return;
- }
- socket.emit('posts.flag', pid, function(err) {
- if (err) {
- return app.alertError(err.message);
- }
- app.alertSuccess('[[topic:flag_success]]');
- });
- });
- });
- }
function openChat(button) {
var post = button.parents('[data-pid]');
diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js
index fd5f310b59..784a11c4a4 100644
--- a/public/src/client/topic/posts.js
+++ b/public/src/client/topic/posts.js
@@ -28,6 +28,8 @@ define('forum/topic/posts', [
});
updatePostCounts(data.posts);
+ ajaxify.data.postcount ++;
+ postTools.updatePostCount(ajaxify.data.postcount);
if (config.usePagination) {
onNewPostPagination(data);
@@ -51,15 +53,15 @@ define('forum/topic/posts', [
var posts = data.posts;
- pagination.pageCount = Math.max(1, Math.ceil((posts[0].topic.postcount - 1) / config.postsPerPage));
+ ajaxify.data.pagination.pageCount = Math.max(1, Math.ceil((posts[0].topic.postcount - 1) / config.postsPerPage));
var direction = config.topicPostSort === 'oldest_to_newest' || config.topicPostSort === 'most_votes' ? 1 : -1;
- var isPostVisible = (pagination.currentPage === pagination.pageCount && direction === 1) || (pagination.currentPage === 1 && direction === -1);
+ var isPostVisible = (ajaxify.data.pagination.currentPage === ajaxify.data.pagination.pageCount && direction === 1) || (ajaxify.data.pagination.currentPage === 1 && direction === -1);
if (isPostVisible) {
createNewPosts(data, components.get('post').not('[data-index=0]'), direction, scrollToPost);
} else if (parseInt(posts[0].uid, 10) === parseInt(app.user.uid, 10)) {
- pagination.loadPage(pagination.pageCount, scrollToPost);
+ pagination.loadPage(ajaxify.data.pagination.pageCount, scrollToPost);
}
}
@@ -220,17 +222,15 @@ define('forum/topic/posts', [
$this.wrap('');
}
});
- postTools.updatePostCount();
- addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote'));
+
+ addBlockquoteEllipses(posts.find('[component="post/content"] > blockquote > blockquote'));
hidePostToolsForDeletedPosts(posts);
- showBottomPostBar();
+ Posts.showBottomPostBar();
};
- function showBottomPostBar() {
- if (components.get('post').length > 1 || !components.get('post', 'index', 0).length) {
- $('.bottom-post-bar').removeClass('hidden');
- }
- }
+ Posts.showBottomPostBar = function() {
+ $('.bottom-post-bar').toggleClass('hidden', components.get('post').length <= 1 && !!components.get('post', 'index', 0).length);
+ };
function hidePostToolsForDeletedPosts(posts) {
posts.each(function() {
diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js
index 9bf07d72c5..3f178c3b85 100644
--- a/public/src/modules/helpers.js
+++ b/public/src/modules/helpers.js
@@ -10,8 +10,11 @@
var helpers = {};
helpers.displayMenuItem = function(data, index) {
- var item = data.navigation[index],
- properties = item.properties;
+ var item = data.navigation[index];
+ if (!item) {
+ return false;
+ }
+ var properties = item.properties;
if (properties) {
if ((properties.loggedIn && !data.config.loggedIn) ||
@@ -37,7 +40,7 @@
property = tag.property ? 'property="' + tag.property + '" ' : '',
content = tag.content ? 'content="' + tag.content.replace(/\n/g, ' ') + '" ' : '';
- return '';
+ return '\n\t';
};
helpers.buildLinkTag = function(tag) {
@@ -47,7 +50,7 @@
href = tag.href ? 'href="' + tag.href + '" ' : '',
sizes = tag.sizes ? 'sizes="' + tag.sizes + '" ' : '';
- return '';
+ return '\n\t';
};
helpers.stringify = function(obj) {
diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js
index 251cc78182..d635eae181 100644
--- a/public/src/modules/navigator.js
+++ b/public/src/modules/navigator.js
@@ -18,7 +18,7 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
toTop = toTop || function() {};
toBottom = toBottom || function() {};
- $(window).on('scroll', navigator.update);
+ $(window).off('scroll', navigator.update).on('scroll', navigator.update);
$('.pagination-block .dropdown-menu').off('click').on('click', function(e) {
e.stopPropagation();
@@ -74,7 +74,12 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
toggle(true);
};
- navigator.hide = function() {
+ navigator.disable = function() {
+ count = 0;
+ index = 1;
+ navigator.selector = navigator.callback = null;
+ $(window).off('scroll', navigator.update);
+
toggle(false);
};
@@ -92,13 +97,19 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
var middleOfViewport = $(window).scrollTop() + $(window).height() / 2;
- index = parseInt($(navigator.selector).first().attr('data-index'), 10);
-
+ index = parseInt($(navigator.selector).first().attr('data-index'), 10) + 1;
+ var previousDistance = Number.MAX_VALUE;
$(navigator.selector).each(function() {
- index++;
- if ($(this).offset().top > middleOfViewport) {
+ var distanceToMiddle = Math.abs(middleOfViewport - $(this).offset().top);
+
+ if (distanceToMiddle > previousDistance) {
return false;
}
+
+ if (distanceToMiddle < previousDistance) {
+ index = parseInt($(this).attr('data-index'), 10) + 1;
+ previousDistance = distanceToMiddle;
+ }
});
if (typeof navigator.callback === 'function') {
@@ -162,7 +173,7 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com
if (config.usePagination) {
var page = Math.max(1, Math.ceil(postIndex / config.postsPerPage));
- if (parseInt(page, 10) !== pagination.currentPage) {
+ if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) {
pagination.loadPage(page, function() {
navigator.scrollToPostIndex(postIndex, highlight, duration);
});
diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js
index 03f5e7b2d5..cb6a4ef04e 100644
--- a/public/src/modules/notifications.js
+++ b/public/src/modules/notifications.js
@@ -64,14 +64,6 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound,
Notifications.updateNotifCount(count);
}
- socket.emit('notifications.getCount', function(err, count) {
- if (!err) {
- Notifications.updateNotifCount(count);
- } else {
- Notifications.updateNotifCount(0);
- }
- });
-
socket.on('event:new_notification', function(notifData) {
app.alert({
alert_id: 'new_notif',
diff --git a/public/src/modules/sort.js b/public/src/modules/sort.js
index a6fe38f90b..960e21facb 100644
--- a/public/src/modules/sort.js
+++ b/public/src/modules/sort.js
@@ -10,7 +10,7 @@ define('sort', ['components'], function(components) {
var currentSetting = threadSort.find('a[data-sort="' + config[field] + '"]');
currentSetting.find('i').addClass('fa-check');
- components.get('topic').on('click', '[component="thread/sort"] a', function() {
+ $('.category, .topic').on('click', '[component="thread/sort"] a', function() {
var newSetting = $(this).attr('data-sort');
socket.emit(method, newSetting, function(err) {
if (err) {
diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js
index 3314500ce5..4a6bb80f51 100644
--- a/public/src/modules/sounds.js
+++ b/public/src/modules/sounds.js
@@ -5,31 +5,30 @@ define('sounds', ['buzz'], function(buzz) {
var Sounds = {};
var loadedSounds = {};
- var eventSoundMapping = {};
- var files = {};
-
- loadFiles();
-
- loadMapping();
+ var eventSoundMapping;
+ var files;
socket.on('event:sounds.reloadMapping', loadMapping);
- function loadFiles() {
- socket.emit('modules.sounds.getSounds', function(err, sounds) {
- if (err) {
- return app.alertError('[sounds] Could not initialise!');
- }
-
- files = sounds;
- });
- }
-
- function loadMapping() {
+ function loadMapping(callback) {
+ callback = callback || function() {};
socket.emit('modules.sounds.getMapping', function(err, mapping) {
if (err) {
return app.alertError('[sounds] Could not load sound mapping!');
}
eventSoundMapping = mapping;
+ callback();
+ });
+ }
+
+ function loadData(callback) {
+ socket.emit('modules.sounds.getData', function(err, data) {
+ if (err) {
+ return app.alertError('[sounds] Could not load sound mapping!');
+ }
+ eventSoundMapping = data.mapping;
+ files = data.files;
+ callback();
});
}
@@ -38,22 +37,37 @@ define('sounds', ['buzz'], function(buzz) {
}
function loadFile(fileName, callback) {
+ function createSound() {
+ if (files && files[fileName]) {
+ loadedSounds[fileName] = new buzz.sound(files[fileName]);
+ }
+ callback();
+ }
+
if (isSoundLoaded(fileName)) {
return callback();
}
- if (files && files[fileName]) {
- loadedSounds[fileName] = new buzz.sound(files[fileName]);
+ if (!files || !files[fileName]) {
+ return loadData(createSound);
}
- callback();
+ createSound();
}
Sounds.play = function(name) {
+ function play() {
+ Sounds.playFile(eventSoundMapping[name]);
+ }
+
if (!config.notificationSounds) {
return;
}
- Sounds.playFile(eventSoundMapping[name]);
+ if (!eventSoundMapping) {
+ return loadData(play);
+ }
+
+ play();
};
Sounds.playFile = function(fileName) {
diff --git a/public/src/utils.js b/public/src/utils.js
index 930e03501f..a2f8ea7a80 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -381,6 +381,12 @@
};
}
+ if (typeof String.prototype.rtrim != 'function') {
+ String.prototype.rtrim = function() {
+ return this.replace(/\s+$/g, '');
+ };
+ }
+
if ('undefined' !== typeof window) {
window.utils = module.exports;
}
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js b/public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js
new file mode 100644
index 0000000000..f4d92ad209
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.it-short.js
@@ -0,0 +1,20 @@
+// Italian shortened
+jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "",
+ suffixFromNow: "",
+ seconds: "1m",
+ minute: "1m",
+ minutes: "%dm",
+ hour: "1h",
+ hours: "%dh",
+ day: "1g",
+ days: "%dg",
+ month: "1me",
+ months: "%dme",
+ year: "1a",
+ years: "%da",
+ wordSeparator: " ",
+ numbers: []
+};
\ No newline at end of file
diff --git a/src/categories/data.js b/src/categories/data.js
index c844e632ad..cccacc8a68 100644
--- a/src/categories/data.js
+++ b/src/categories/data.js
@@ -53,17 +53,11 @@ module.exports = function(Categories) {
}
if (category.description) {
- plugins.fireHook('filter:parse.raw', category.description, function(err, parsedDescription) {
- if (err) {
- return callback(err);
- }
- category.descriptionParsed = parsedDescription;
- category.description = validator.escape(category.description);
- callback(null, category);
- });
- } else {
- callback(null, category);
+ category.description = validator.escape(category.description);
+ category.descriptionParsed = category.descriptionParsed || category.description;
}
+
+ callback(null, category);
}
Categories.getCategoryField = function(cid, field, callback) {
diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js
index d7a2a165a0..84cb005de0 100644
--- a/src/categories/recentreplies.js
+++ b/src/categories/recentreplies.js
@@ -3,14 +3,13 @@
var async = require('async'),
winston = require('winston'),
+ validator = require('validator'),
_ = require('underscore'),
- meta = require('../meta'),
db = require('../database'),
posts = require('../posts'),
topics = require('../topics'),
- privileges = require('../privileges'),
- plugins = require('../plugins');
+ privileges = require('../privileges');
module.exports = function(Categories) {
Categories.getRecentReplies = function(cid, uid, count, callback) {
@@ -38,25 +37,21 @@ module.exports = function(Categories) {
async.waterfall([
function(next) {
- async.map(categoryData, getRecentTopicPids, next);
+ async.map(categoryData, getRecentTopicTids, next);
},
function(results, next) {
- var pids = _.flatten(results);
+ var tids = _.flatten(results);
- pids = pids.filter(function(pid, index, array) {
- return !!pid && array.indexOf(pid) === index;
+ tids = tids.filter(function(tid, index, array) {
+ return !!tid && array.indexOf(tid) === index;
});
- privileges.posts.filter('read', pids, uid, next);
+ privileges.topics.filterTids('read', tids, uid, next);
},
- function(pids, next) {
- if (meta.config.teaserPost === 'first') {
- getMainPosts(pids, uid, next);
- } else {
- posts.getPostSummaryByPids(pids, uid, {stripTags: true}, next);
- }
+ function(tids, next) {
+ getTopics(tids, next);
},
- function(posts, next) {
- assignPostsToCategories(categoryData, posts);
+ function(topics, next) {
+ assignTopicsToCategories(categoryData, topics);
bubbleUpChildrenPosts(categoryData);
@@ -65,29 +60,86 @@ module.exports = function(Categories) {
], callback);
};
- function getMainPosts(pids, uid, callback) {
+ function getRecentTopicTids(category, callback) {
+ var count = parseInt(category.numRecentReplies, 10);
+ if (!count) {
+ return callback(null, []);
+ }
+
+ if (count === 1) {
+ async.waterfall([
+ function (next) {
+ db.getSortedSetRevRange('cid:' + category.cid + ':pids', 0, 0, next);
+ },
+ function (pid, next) {
+ posts.getPostField(pid, 'tid', next);
+ },
+ function (tid, next) {
+ next(null, [tid]);
+ }
+ ], callback);
+ return;
+ }
+
+ async.parallel({
+ pinnedTids: function(next) {
+ db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, -1, '+inf', Date.now(), next);
+ },
+ tids: function(next) {
+ db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, Math.max(1, count), Date.now(), 0, next);
+ }
+ }, function(err, results) {
+ if (err) {
+ return callback(err);
+ }
+
+ results.tids = results.tids.concat(results.pinnedTids);
+
+ callback(null, results.tids);
+ });
+ }
+
+ function getTopics(tids, callback) {
+ var topicData;
async.waterfall([
- function(next) {
- var keys = pids.map(function(pid) {
- return 'post:' + pid;
- });
- db.getObjectsFields(keys, ['tid'], next);
+ function (next) {
+ topics.getTopicsFields(tids, ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'], next);
},
- function(posts, next) {
- var keys = posts.map(function(post) {
- return 'topic:' + post.tid;
+ function (_topicData, next) {
+ topicData = _topicData;
+ topicData.forEach(function(topic) {
+ topic.teaserPid = topic.teaserPid || topic.mainPid;
});
- db.getObjectsFields(keys, ['mainPid'], next);
+
+ topics.getTeasers(topicData, next);
},
- function(topics, next) {
- var mainPids = topics.map(function(topic) {
- return topic.mainPid;
+ function (teasers, next) {
+ teasers.forEach(function(teaser, index) {
+ if (teaser) {
+ teaser.cid = topicData[index].cid;
+ teaser.tid = teaser.uid = teaser.user.uid = undefined;
+ teaser.topic = {
+ slug: topicData[index].slug,
+ title: validator.escape(topicData[index].title)
+ };
+ }
});
- posts.getPostSummaryByPids(mainPids, uid, {stripTags: true}, next);
+ teasers = teasers.filter(Boolean);
+ next(null, teasers);
}
], callback);
}
+ function assignTopicsToCategories(categories, topics) {
+ categories.forEach(function(category) {
+ category.posts = topics.filter(function(topic) {
+ return topic.cid && parseInt(topic.cid, 10) === parseInt(category.cid, 10);
+ }).sort(function(a, b) {
+ return b.pid - a.pid;
+ }).slice(0, parseInt(category.numRecentReplies, 10));
+ });
+ }
+
function bubbleUpChildrenPosts(categoryData) {
categoryData.forEach(function(category) {
if (category.posts.length) {
@@ -97,7 +149,7 @@ module.exports = function(Categories) {
getPostsRecursive(category, posts);
posts.sort(function(a, b) {
- return b.timestamp - a.timestamp;
+ return b.pid - a.pid;
});
if (posts.length) {
category.posts = [posts[0]];
@@ -115,64 +167,6 @@ module.exports = function(Categories) {
});
}
- function assignPostsToCategories(categories, posts) {
- categories.forEach(function(category) {
- category.posts = posts.filter(function(post) {
- return post.category && (parseInt(post.category.cid, 10) === parseInt(category.cid, 10) ||
- parseInt(post.category.parentCid, 10) === parseInt(category.cid, 10));
- }).sort(function(a, b) {
- return b.timestamp - a.timestamp;
- }).slice(0, parseInt(category.numRecentReplies, 10));
- });
- }
-
- function getRecentTopicPids(category, callback) {
- var count = parseInt(category.numRecentReplies, 10);
- if (!count) {
- return callback(null, []);
- }
-
- db.getSortedSetRevRange('cid:' + category.cid + ':pids', 0, 0, function(err, pids) {
- if (err || !Array.isArray(pids) || !pids.length) {
- return callback(err, []);
- }
-
- if (count === 1) {
- return callback(null, pids);
- }
-
- async.parallel({
- pinnedTids: function(next) {
- db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, -1, '+inf', Date.now(), next);
- },
- tids: function(next) {
- db.getSortedSetRevRangeByScore('cid:' + category.cid + ':tids', 0, Math.max(0, count), Date.now(), 0, next);
- }
- }, function(err, results) {
- if (err) {
- return callback(err);
- }
-
- results.tids = results.tids.concat(results.pinnedTids);
-
- async.map(results.tids, topics.getLatestUndeletedPid, function(err, topicPids) {
- if (err) {
- return callback(err);
- }
-
- pids = pids.concat(topicPids).filter(function(pid, index, array) {
- return !!pid && array.indexOf(pid) === index;
- }).sort(function(a, b) {
- return b - a;
- }).slice(0, count);
-
- callback(null, pids);
- });
- });
- });
- }
-
-
Categories.moveRecentReplies = function(tid, oldCid, cid) {
updatePostCount(tid, oldCid, cid);
topics.getPids(tid, function(err, pids) {
diff --git a/src/categories/update.js b/src/categories/update.js
index 4ebfda2f11..ffd24e7d98 100644
--- a/src/categories/update.js
+++ b/src/categories/update.js
@@ -68,6 +68,8 @@ module.exports = function(Categories) {
if (key === 'order') {
updateOrder(cid, value, callback);
+ } else if (key === 'description') {
+ parseDescription(cid, value, callback);
} else {
callback();
}
@@ -119,4 +121,13 @@ module.exports = function(Categories) {
});
}
+ function parseDescription(cid, description, callback) {
+ plugins.fireHook('filter:parse.raw', description, function(err, parsedDescription) {
+ if (err) {
+ return callback(err);
+ }
+ Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, callback);
+ });
+ }
+
};
diff --git a/src/controllers/accounts/chats.js b/src/controllers/accounts/chats.js
index 11f6535d8c..1f95db984b 100644
--- a/src/controllers/accounts/chats.js
+++ b/src/controllers/accounts/chats.js
@@ -19,7 +19,7 @@ chatsController.get = function(req, res, callback) {
// In case a userNAME is passed in instead of a slug, the route should not 404
var slugified = utils.slugify(req.params.userslug);
if (req.params.userslug && req.params.userslug !== slugified) {
- return res.redirect(nconf.get('relative_path') + '/chats/' + slugified);
+ return helpers.redirect(res, '/chats/' + slugified);
}
async.parallel({
diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js
index 1120ad65e3..a84dc7c991 100644
--- a/src/controllers/accounts/edit.js
+++ b/src/controllers/accounts/edit.js
@@ -14,6 +14,48 @@ var async = require('async'),
var editController = {};
editController.get = function(req, res, callback) {
+ accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, function(err, userData) {
+ if (err || !userData) {
+ return callback(err);
+ }
+
+ userData.title = '[[pages:account/edit, ' + userData.username + ']]';
+ userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]);
+
+ res.render('account/edit', userData);
+ });
+};
+
+editController.password = function(req, res, next) {
+ renderRoute('password', req, res, next);
+};
+
+editController.username = function(req, res, next) {
+ renderRoute('username', req, res, next);
+};
+
+editController.email = function(req, res, next) {
+ renderRoute('email', req, res, next);
+};
+
+function renderRoute(name, req, res, next) {
+ getUserData(req, next, function(err, userData) {
+ if (err) {
+ return next(err);
+ }
+
+ userData.title = '[[pages:account/edit/' + name + ', ' + userData.username + ']]';
+ userData.breadcrumbs = helpers.buildBreadcrumbs([
+ {text: userData.username, url: '/user/' + userData.userslug},
+ {text: '[[user:edit]]', url: '/user/' + userData.userslug + '/edit'},
+ {text: '[[user:' + name + ']]'}
+ ]);
+
+ res.render('account/edit/' + name, userData);
+ });
+}
+
+function getUserData(req, next, callback) {
var userData;
async.waterfall([
function(next) {
@@ -22,7 +64,7 @@ editController.get = function(req, res, callback) {
function(data, next) {
userData = data;
if (!userData) {
- return callback();
+ return next();
}
db.getObjectField('user:' + userData.uid, 'password', next);
}
@@ -33,13 +75,9 @@ editController.get = function(req, res, callback) {
userData['username:disableEdit'] = parseInt(meta.config['username:disableEdit'], 10) === 1;
userData.hasPassword = !!password;
- userData.title = '[[pages:account/edit, ' + userData.username + ']]';
- userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]);
-
- res.render('account/edit', userData);
+ callback(null, userData);
});
-};
-
+}
editController.uploadPicture = function (req, res, next) {
var userPhoto = req.files.files[0];
diff --git a/src/controllers/accounts/notifications.js b/src/controllers/accounts/notifications.js
index 2f540e9d94..aa60892f47 100644
--- a/src/controllers/accounts/notifications.js
+++ b/src/controllers/accounts/notifications.js
@@ -7,12 +7,13 @@ var user = require('../../user'),
var notificationsController = {};
notificationsController.get = function(req, res, next) {
- user.notifications.getAll(req.uid, 40, function(err, notifications) {
+ user.notifications.getAll(req.uid, 0, 39, function(err, notifications) {
if (err) {
return next(err);
}
res.render('notifications', {
notifications: notifications,
+ nextStart: 40,
title: '[[pages:notifications]]',
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[pages:notifications]]'}])
});
diff --git a/src/controllers/admin.js b/src/controllers/admin.js
index b620dce3d2..0580bd42a4 100644
--- a/src/controllers/admin.js
+++ b/src/controllers/admin.js
@@ -24,7 +24,8 @@ var adminController = {
navigation: require('./admin/navigation'),
themes: require('./admin/themes'),
users: require('./admin/users'),
- uploads: require('./admin/uploads')
+ uploads: require('./admin/uploads'),
+ info: require('./admin/info')
};
diff --git a/src/controllers/admin/groups.js b/src/controllers/admin/groups.js
index 259f93759d..dc8bf6ff82 100644
--- a/src/controllers/admin/groups.js
+++ b/src/controllers/admin/groups.js
@@ -59,12 +59,13 @@ groupsController.get = function(req, res, callback) {
if (!exists) {
return callback();
}
- groups.get(groupName, {uid: req.uid}, next);
+ groups.get(groupName, {uid: req.uid, truncateUserList: true, userListCount: 20}, next);
}
], function(err, group) {
if (err) {
return callback(err);
}
+ group.isOwner = true;
res.render('admin/manage/group', {group: group});
});
};
diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js
new file mode 100644
index 0000000000..a9a489f6c4
--- /dev/null
+++ b/src/controllers/admin/info.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var os = require('os');
+
+var infoController = {};
+
+infoController.get = function(req, res, next) {
+
+ var data = {
+ process: {
+ pid: process.pid,
+ title: process.title,
+ arch: process.arch,
+ platform: process.platform,
+ version: process.version,
+ versions: process.versions,
+ memoryUsage: process.memoryUsage(),
+ uptime: process.uptime()
+ },
+ os: {
+ hostname: os.hostname()
+ }
+ };
+
+ res.render('admin/development/info', {info: JSON.stringify(data, null, 4)});
+};
+
+
+module.exports = infoController;
\ No newline at end of file
diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js
index 16242fe18b..eb6a4a59f4 100644
--- a/src/controllers/admin/uploads.js
+++ b/src/controllers/admin/uploads.js
@@ -114,7 +114,7 @@ function validateUpload(req, res, next, uploadedFile, allowedTypes) {
}
});
- res.json({error: '[[error:invalid-image-type, ' + allowedTypes.join(', ') + ']]'});
+ res.json({error: '[[error:invalid-image-type, ' + allowedTypes.join(', ') + ']]'});
return false;
}
diff --git a/src/controllers/api.js b/src/controllers/api.js
index 56308ef535..421673e7c4 100644
--- a/src/controllers/api.js
+++ b/src/controllers/api.js
@@ -84,6 +84,7 @@ apiController.getConfig = function(req, res, next) {
config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
config.csrf_token = req.csrfToken();
config.searchEnabled = plugins.hasListeners('filter:search.query');
+ config.bootswatchSkin = 'default';
if (!req.user) {
return filterConfig();
@@ -103,6 +104,7 @@ apiController.getConfig = function(req, res, next) {
config.topicPostSort = settings.topicPostSort || config.topicPostSort;
config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
config.topicSearchEnabled = settings.topicSearchEnabled || false;
+ config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin;
filterConfig();
});
diff --git a/src/controllers/categories.js b/src/controllers/categories.js
index 220779c524..b2d36b91e8 100644
--- a/src/controllers/categories.js
+++ b/src/controllers/categories.js
@@ -65,6 +65,15 @@ categoriesController.list = function(req, res, next) {
return next(err);
}
+ data.categories.forEach(function(category) {
+ if (category && Array.isArray(category.posts) && category.posts.length) {
+ category.teaser = {
+ url: nconf.get('relative_path') + '/topic/' + category.posts[0].topic.slug + '/' + category.posts[0].index,
+ timestampISO: category.posts[0].timestamp
+ };
+ }
+ });
+
data.title = '[[pages:categories]]';
if (req.path.startsWith('/api/categories') || req.path.startsWith('/categories')) {
data.breadcrumbs = helpers.buildBreadcrumbs([{text: data.title}]);
@@ -81,7 +90,7 @@ categoriesController.list = function(req, res, next) {
categoriesController.get = function(req, res, callback) {
var cid = req.params.category_id,
- page = parseInt(req.query.page, 10) || 1,
+ currentPage = parseInt(req.query.page, 10) || 1,
pageCount = 1,
userPrivileges;
@@ -127,7 +136,7 @@ categoriesController.get = function(req, res, callback) {
return helpers.redirect(res, '/category/' + cid + '/' + req.params.slug + (topicIndex > topicCount ? '/' + topicCount : ''));
}
- if (settings.usePagination && (page < 1 || page > pageCount)) {
+ if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) {
return callback();
}
@@ -135,7 +144,7 @@ categoriesController.get = function(req, res, callback) {
topicIndex = Math.max(topicIndex - (settings.topicsPerPage - 1), 0);
} else if (!req.query.page) {
var index = Math.max(parseInt((topicIndex || 0), 10), 0);
- page = Math.ceil((index + 1) / settings.topicsPerPage);
+ currentPage = Math.ceil((index + 1) / settings.topicsPerPage);
topicIndex = 0;
}
@@ -149,7 +158,7 @@ categoriesController.get = function(req, res, callback) {
set = 'cid:' + cid + ':tids:posts';
}
- var start = (page - 1) * settings.topicsPerPage + topicIndex,
+ var start = (currentPage - 1) * settings.topicsPerPage + topicIndex,
stop = start + settings.topicsPerPage - 1;
next(null, {
@@ -194,8 +203,11 @@ categoriesController.get = function(req, res, callback) {
});
},
function(categoryData, next) {
+ if (!categoryData.children.length) {
+ return next(null, categoryData);
+ }
var allCategories = [];
- categories.flattenCategories(allCategories, [categoryData]);
+ categories.flattenCategories(allCategories, categoryData.children);
categories.getRecentTopicReplies(allCategories, req.uid, function(err) {
next(err, categoryData);
});
@@ -249,12 +261,10 @@ categoriesController.get = function(req, res, callback) {
return callback(err);
}
- data.currentPage = page;
- data.pageCount = pageCount;
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.rssFeedUrl = nconf.get('relative_path') + '/category/' + data.cid + '.rss';
data.title = data.name;
- data.pagination = pagination.create(data.currentPage, data.pageCount);
+ data.pagination = pagination.create(currentPage, pageCount);
data.pagination.rel.forEach(function(rel) {
rel.href = nconf.get('url') + '/category/' + data.slug + rel.href;
res.locals.linkTags.push(rel);
diff --git a/src/controllers/search.js b/src/controllers/search.js
index 329f599783..e32ab67c4b 100644
--- a/src/controllers/search.js
+++ b/src/controllers/search.js
@@ -2,7 +2,8 @@
'use strict';
var async = require('async'),
- validator = require('validator'),
+
+ meta = require('../meta'),
plugins = require('../plugins'),
search = require('../search'),
categories = require('../categories'),
@@ -17,6 +18,10 @@ searchController.search = function(req, res, next) {
return next();
}
+ if (!req.user && parseInt(meta.config.allowGuestSearching, 10) !== 1) {
+ return helpers.notAllowed(req, res);
+ }
+
var page = Math.max(1, parseInt(req.query.page, 10)) || 1;
if (req.query.categories && !Array.isArray(req.query.categories)) {
req.query.categories = [req.query.categories];
@@ -51,6 +56,7 @@ searchController.search = function(req, res, next) {
searchData.pagination = pagination.create(page, searchData.pageCount, req.query);
searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts';
searchData.showAsTopics = req.query.showAs === 'topics';
+ searchData.title = '[[global:header.search]]';
searchData.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:search]]'}]);
searchData.expandSearch = !req.params.term;
diff --git a/src/controllers/topics.js b/src/controllers/topics.js
index abbb245f28..af62540ff6 100644
--- a/src/controllers/topics.js
+++ b/src/controllers/topics.js
@@ -18,6 +18,8 @@ var topicsController = {},
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)) {
@@ -56,14 +58,13 @@ topicsController.get = function(req, res, callback) {
var settings = results.settings;
var postCount = parseInt(results.topic.postcount, 10);
- var pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage));
- var page = parseInt(req.query.page, 10) || 1;
+ 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 && (page < 1 || page > pageCount)) {
+ if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) {
return callback();
}
@@ -105,10 +106,10 @@ topicsController.get = function(req, res, callback) {
index = Math.max(0, req.params.post_index - 1) || 0;
}
- page = Math.max(1, Math.ceil(index / settings.postsPerPage));
+ currentPage = Math.max(1, Math.ceil(index / settings.postsPerPage));
}
- var start = (page - 1) * settings.postsPerPage + postIndex,
+ var start = (currentPage - 1) * settings.postsPerPage + postIndex,
stop = start + settings.postsPerPage - 1;
topics.getTopicWithPosts(tid, set, req.uid, start, stop, reverse, function (err, topicData) {
@@ -120,9 +121,6 @@ topicsController.get = function(req, res, callback) {
return next(err);
}
- topicData.pageCount = pageCount;
- topicData.currentPage = page;
-
topics.modifyByPrivilege(topicData.posts, results.privileges);
plugins.fireHook('filter:controllers.topic.get', topicData, next);
@@ -238,7 +236,7 @@ topicsController.get = function(req, res, callback) {
},
{
rel: 'canonical',
- href: nconf.get('url') + '/topic/' + topicData.slug
+ href: nconf.get('url') + '/topic/' + topicData.slug + (currentPage > 1 ? '?page=' + currentPage : '')
}
];
@@ -261,7 +259,7 @@ topicsController.get = function(req, res, callback) {
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(data.currentPage, data.pageCount);
+ 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);
diff --git a/src/controllers/users.js b/src/controllers/users.js
index 96c553a554..0c8e03f2c0 100644
--- a/src/controllers/users.js
+++ b/src/controllers/users.js
@@ -58,6 +58,9 @@ usersController.getUsersSortedByPosts = function(req, res, next) {
};
usersController.getUsersSortedByReputation = function(req, res, next) {
+ if (parseInt(meta.config['reputation:disabled'], 10) === 1) {
+ return next();
+ }
usersController.getUsers('users:reputation', 0, 49, req, res, next);
};
@@ -217,6 +220,7 @@ usersController.getMap = function(req, res, next) {
res.render('usersMap', {
rooms: data,
+ 'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1,
title: '[[pages:users/map]]',
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[global:users]]', url: '/users'}, {text: '[[global:map]]'}])
});
@@ -230,6 +234,7 @@ function render(req, res, data, next) {
}
data.templateData.inviteOnly = meta.config.registrationType === 'invite-only';
+ data.templateData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
res.render('users', data.templateData);
});
}
diff --git a/src/database/mongo.js b/src/database/mongo.js
index 49f2da5edd..0563e02633 100644
--- a/src/database/mongo.js
+++ b/src/database/mongo.js
@@ -178,25 +178,55 @@
module.info = function(db, callback) {
async.parallel({
- serverStats: function(next) {
+ serverStatus: function(next) {
db.command({'serverStatus': 1}, next);
},
stats: function(next) {
- db.stats({scale:1024}, next);
+ db.command({'dbStats': 1}, next);
+ },
+ listCollections: function(next) {
+ db.listCollections().toArray(function(err, items) {
+ if (err) {
+ return next(err);
+ }
+ async.map(items, function(collection, next) {
+ db.collection(collection.name).stats(next);
+ }, next);
+ });
}
}, function(err, results) {
if (err) {
return callback(err);
}
var stats = results.stats;
+ var scale = 1024 * 1024;
+
+ results.listCollections = results.listCollections.map(function(collectionInfo) {
+ return {
+ name: collectionInfo.ns,
+ count: collectionInfo.count,
+ size: collectionInfo.size,
+ avgObjSize: collectionInfo.avgObjSize,
+ storageSize: collectionInfo.storageSize,
+ totalIndexSize: collectionInfo.totalIndexSize,
+ indexSizes: collectionInfo.indexSizes
+ };
+ });
+
+ stats.mem = results.serverStatus.mem;
+ stats.collectionData = results.listCollections;
+ stats.network = results.serverStatus.network;
+ stats.raw = JSON.stringify(stats, null, 4);
stats.avgObjSize = (stats.avgObjSize / 1024).toFixed(2);
- stats.dataSize = (stats.dataSize / 1024).toFixed(2);
- stats.storageSize = (stats.storageSize / 1024).toFixed(2);
- stats.fileSize = (stats.fileSize / 1024).toFixed(2);
- stats.indexSize = (stats.indexSize / 1024).toFixed(2);
- stats.mem = results.serverStats.mem;
- stats.raw = JSON.stringify(stats, null, 4);
+ stats.dataSize = (stats.dataSize / scale).toFixed(2);
+ stats.storageSize = (stats.storageSize / scale).toFixed(2);
+ stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0;
+ stats.indexSize = (stats.indexSize / scale).toFixed(2);
+ stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1';
+ stats.host = results.serverStatus.host;
+ stats.version = results.serverStatus.version;
+ stats.uptime = results.serverStatus.uptime;
stats.mongo = true;
callback(null, stats);
diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js
index 0be79b5388..db84dbb369 100644
--- a/src/database/mongo/helpers.js
+++ b/src/database/mongo/helpers.js
@@ -6,6 +6,7 @@ helpers.toMap = function(data) {
var map = {};
for (var i = 0; i';
- str = (res.locals.postHeader ? res.locals.postHeader : '') + str + (res.locals.preFooter ? res.locals.preFooter : '');
-
- if (res.locals.footer) {
- str = str + res.locals.footer;
- } else if (res.locals.adminFooter) {
- str = str + res.locals.adminFooter;
- }
-
- if (res.locals.renderHeader || res.locals.renderAdminHeader) {
- var method = res.locals.renderHeader ? middleware.renderHeader : middleware.admin.renderHeader;
- method(req, res, options, function(err, template) {
- if (err) {
- return fn(err);
- }
- str = template + str;
- var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB';
- language = req.query.lang || language;
- translator.translate(str, language, function(translated) {
- fn(err, translated);
- });
- });
- } else {
- fn(err, str);
- }
- });
- };
-
- next();
-};
-
middleware.routeTouchIcon = function(req, res) {
if (meta.config['brand:logo'] && validator.isURL(meta.config['brand:logo'])) {
return res.redirect(meta.config['brand:logo']);
@@ -403,8 +169,6 @@ middleware.addExpiresHeaders = function(req, res, next) {
next();
};
-
-
middleware.privateTagListing = function(req, res, next) {
if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) {
controllers.helpers.notAllowed(req, res);
@@ -414,32 +178,26 @@ middleware.privateTagListing = function(req, res, next) {
};
middleware.exposeGroupName = function(req, res, next) {
- if (!req.params.hasOwnProperty('slug')) { return next(); }
+ expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next);
+};
- groups.getGroupNameByGroupSlug(req.params.slug, function(err, groupName) {
+middleware.exposeUid = function(req, res, next) {
+ expose('uid', user.getUidByUserslug, 'userslug', req, res, next);
+};
+
+function expose(exposedField, method, field, req, res, next) {
+ if (!req.params.hasOwnProperty(field)) {
+ return next();
+ }
+ method(req.params[field], function(err, id) {
if (err) {
return next(err);
}
- res.locals.groupName = groupName;
+ res.locals[exposedField] = id;
next();
});
-};
-
-middleware.exposeUid = function(req, res, next) {
- if (req.params.hasOwnProperty('userslug')) {
- user.getUidByUserslug(req.params.userslug, function(err, uid) {
- if (err) {
- return next(err);
- }
-
- res.locals.uid = uid;
- next();
- });
- } else {
- next();
- }
-};
+}
middleware.requireUser = function(req, res, next) {
if (req.user) {
@@ -449,32 +207,23 @@ middleware.requireUser = function(req, res, next) {
res.render('403', {title: '[[global:403.title]]'});
};
-function redirectToLogin(req, res) {
- req.session.returnTo = nconf.get('relative_path') + req.url.replace(/^\/api/, '');
- return controllers.helpers.redirect(res, '/login');
-}
-
-
-
-function modifyTitle(obj) {
- var title = controllers.helpers.buildTitle('[[pages:home]]');
- obj.browserTitle = title;
-
- if (obj.metaTags) {
- obj.metaTags.forEach(function(tag, i) {
- if (tag.property === 'og:title') {
- obj.metaTags[i].content = title;
- }
- });
+middleware.privateUploads = function(req, res, next) {
+ if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) {
+ return next();
}
+ if (req.path.startsWith('/uploads/files')) {
+ return res.status(403).json('not-allowed');
+ }
+ next();
+};
- return title;
-}
module.exports = function(webserver) {
app = webserver;
middleware.admin = require('./admin')(webserver);
+ require('./header')(app, middleware);
+ require('./render')(middleware);
require('./maintenance')(middleware);
return middleware;
diff --git a/src/middleware/render.js b/src/middleware/render.js
new file mode 100644
index 0000000000..4a80d4b9c8
--- /dev/null
+++ b/src/middleware/render.js
@@ -0,0 +1,95 @@
+'use strict';
+
+var nconf = require('nconf');
+var translator = require('../../public/src/modules/translator');
+
+module.exports = function(middleware) {
+
+ middleware.processRender = function(req, res, next) {
+ // res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687
+ var render = res.render;
+ res.render = function(template, options, fn) {
+ var self = this,
+ req = this.req,
+ defaultFn = function(err, str){
+ if (err) {
+ return req.next(err);
+ }
+
+ self.send(str);
+ };
+ options = options || {};
+
+ if ('function' === typeof options) {
+ fn = options;
+ options = {};
+ }
+
+ options.loggedIn = req.user ? parseInt(req.user.uid, 10) !== 0 : false;
+ options.relative_path = nconf.get('relative_path');
+ options.template = {name: template};
+ options.template[template] = true;
+ options.bodyClass = buildBodyClass(req);
+
+ res.locals.template = template;
+
+ if (res.locals.isAPI) {
+ if (req.route && req.route.path === '/api/') {
+ options.title = '[[pages:home]]';
+ }
+
+ return res.json(options);
+ }
+
+ if ('function' !== typeof fn) {
+ fn = defaultFn;
+ }
+
+ var ajaxifyData = encodeURIComponent(JSON.stringify(options));
+ render.call(self, template, options, function(err, str) {
+ if (err) {
+ return fn(err);
+ }
+
+ str = str + '';
+ str = (res.locals.postHeader ? res.locals.postHeader : '') + str + (res.locals.preFooter ? res.locals.preFooter : '');
+
+ if (res.locals.footer) {
+ str = str + res.locals.footer;
+ } else if (res.locals.adminFooter) {
+ str = str + res.locals.adminFooter;
+ }
+
+ if (res.locals.renderHeader || res.locals.renderAdminHeader) {
+ var method = res.locals.renderHeader ? middleware.renderHeader : middleware.admin.renderHeader;
+ method(req, res, options, function(err, template) {
+ if (err) {
+ return fn(err);
+ }
+ str = template + str;
+ var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB';
+ language = req.query.lang || language;
+ translator.translate(str, language, function(translated) {
+ fn(err, translated);
+ });
+ });
+ } else {
+ fn(err, str);
+ }
+ });
+ };
+
+ next();
+ };
+
+ function buildBodyClass(req) {
+ var clean = req.path.replace(/^\/api/, '').replace(/^\//, '');
+ var parts = clean.split('/').slice(0, 3);
+ parts.forEach(function(p, index) {
+ parts[index] = index ? parts[index - 1] + '-' + p : 'page-' + (p || 'home');
+ });
+
+ return parts.join(' ');
+ }
+
+};
\ No newline at end of file
diff --git a/src/navigation/admin.js b/src/navigation/admin.js
index d608e1b620..b66982a310 100644
--- a/src/navigation/admin.js
+++ b/src/navigation/admin.js
@@ -7,6 +7,7 @@ var admin = {},
db = require('../database'),
translator = require('../../public/src/modules/translator');
+var navigationCache = null;
admin.save = function(data, callback) {
var order = Object.keys(data),
@@ -23,6 +24,7 @@ admin.save = function(data, callback) {
return JSON.stringify(data);
});
+ navigationCache = null;
async.waterfall([
function(next) {
db.delete('navigation:enabled', next);
@@ -41,10 +43,19 @@ admin.getAdmin = function(callback) {
};
admin.get = function(callback) {
+ if (navigationCache) {
+ return callback(null, navigationCache);
+ }
+
db.getSortedSetRange('navigation:enabled', 0, -1, function(err, data) {
- callback(err, data.map(function(item, idx) {
+ if (err) {
+ return callback(err);
+ }
+ navigationCache = data.map(function(item, idx) {
return JSON.parse(item)[idx];
- }));
+ });
+
+ callback(null, navigationCache);
});
};
diff --git a/src/navigation/index.js b/src/navigation/index.js
index 6c8be11d2f..7d536dcca9 100644
--- a/src/navigation/index.js
+++ b/src/navigation/index.js
@@ -2,8 +2,6 @@
var navigation = {},
- plugins = require('../plugins'),
- db = require('../database'),
admin = require('./admin'),
translator = require('../../public/src/modules/translator');
diff --git a/src/notifications.js b/src/notifications.js
index 48e59f168a..a3d4e61ef2 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -229,7 +229,7 @@ var async = require('async'),
return callback();
}
- db.getObject('notification:' + nid, function(err, notification) {
+ db.getObject('notifications:' + nid, function(err, notification) {
if (err || !notification) {
return callback(err || new Error('[[error:no-notification]]'));
}
diff --git a/src/pagination.js b/src/pagination.js
index 58813c391b..15b8aa6eb3 100644
--- a/src/pagination.js
+++ b/src/pagination.js
@@ -10,7 +10,9 @@ pagination.create = function(currentPage, pageCount, queryObj) {
prev: {page: 1, active: currentPage > 1},
next: {page: 1, active: currentPage < pageCount},
rel: [],
- pages: []
+ pages: [],
+ currentPage: 1,
+ pageCount: 1
};
}
pageCount = parseInt(pageCount, 10);
@@ -44,7 +46,7 @@ pagination.create = function(currentPage, pageCount, queryObj) {
}
}
- var data = {rel: [], pages: pages};
+ var data = {rel: [], pages: pages, currentPage: currentPage, pageCount: pageCount};
queryObj.page = previous;
data.prev = {page: previous, active: currentPage > 1, qs: qs.stringify(queryObj)};
queryObj.page = next;
diff --git a/src/posts/flags.js b/src/posts/flags.js
index cbfa1f832c..3adb6541f2 100644
--- a/src/posts/flags.js
+++ b/src/posts/flags.js
@@ -9,7 +9,10 @@ var async = require('async'),
module.exports = function(Posts) {
- Posts.flag = function(post, uid, callback) {
+ Posts.flag = function(post, uid, reason, callback) {
+ if (!parseInt(uid, 10) || !reason) {
+ return callback();
+ }
async.parallel({
hasFlagged: async.apply(hasFlagged, post.pid, uid),
exists: async.apply(Posts.exists, post.pid)
@@ -36,6 +39,9 @@ module.exports = function(Posts) {
function(next) {
db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next);
},
+ function(next) {
+ db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next);
+ },
function(next) {
if (parseInt(post.uid, 10)) {
db.sortedSetAdd('uid:' + post.uid + ':flag:pids', now, post.pid, next);
@@ -50,7 +56,7 @@ module.exports = function(Posts) {
next();
}
}
- ], function(err, results) {
+ ], function(err) {
callback(err);
});
});
@@ -80,26 +86,75 @@ module.exports = function(Posts) {
},
function(next) {
db.delete('pid:' + pid + ':flag:uids', next);
+ },
+ function(next) {
+ db.delete('pid:' + pid + ':flag:uid:reason', next);
}
- ], function(err, results) {
+ ], function(err) {
callback(err);
});
};
Posts.dismissAllFlags = function(callback) {
- db.delete('posts:flagged', callback);
- };
-
- Posts.getFlags = function(set, uid, start, stop, callback) {
- db.getSortedSetRevRange(set, start, stop, function(err, pids) {
+ db.getSortedSetRange('posts:flagged', 0, -1, function(err, pids) {
if (err) {
return callback(err);
}
-
- Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags']}, callback);
+ async.eachLimit(pids, 50, Posts.dismissFlag, callback);
});
};
+ Posts.getFlags = function(set, uid, start, stop, callback) {
+ async.waterfall([
+ function (next) {
+ db.getSortedSetRevRange(set, start, stop, next);
+ },
+ function (pids, next) {
+ getFlaggedPostsWithReasons(pids, uid, next);
+ }
+ ], callback);
+ };
+
+ function getFlaggedPostsWithReasons(pids, uid, callback) {
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ uidsReasons: function(next) {
+ async.map(pids, function(pid, next) {
+ db.getSortedSetRange('pid:' + pid + ':flag:uid:reason', 0, -1, next);
+ }, next);
+ },
+ posts: function(next) {
+ Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags']}, next);
+ }
+ }, next);
+ },
+ function (results, next) {
+ async.map(results.uidsReasons, function(uidReasons, next) {
+ async.map(uidReasons, function(uidReason, next) {
+ var uid = uidReason.split(':')[0];
+ var reason = uidReason.substr(uidReason.indexOf(':') + 1);
+ user.getUserFields(uid, ['username', 'userslug', 'picture'], function(err, userData) {
+ next(err, {user: userData, reason: reason});
+ });
+ }, next);
+ }, function(err, reasons) {
+ if (err) {
+ return callback(err);
+ }
+
+ results.posts.forEach(function(post, index) {
+ if (post) {
+ post.flagReasons = reasons[index];
+ }
+ });
+
+ next(null, results.posts);
+ });
+ }
+ ], callback);
+ }
+
Posts.getUserFlags = function(byUsername, sortBy, callerUID, start, stop, callback) {
async.waterfall([
function(next) {
@@ -112,7 +167,7 @@ module.exports = function(Posts) {
db.getSortedSetRevRange('uid:' + uid + ':flag:pids', 0, -1, next);
},
function(pids, next) {
- Posts.getPostSummaryByPids(pids, callerUID, {stripTags: false, extraFields: ['flags']}, next);
+ getFlaggedPostsWithReasons(pids, callerUID, next);
},
function(posts, next) {
if (sortBy === 'count') {
@@ -120,6 +175,7 @@ module.exports = function(Posts) {
return b.flags - a.flags;
});
}
+
next(null, posts.slice(start, stop));
}
], callback);
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index 43518861d7..fa03bca51b 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -179,7 +179,8 @@ module.exports = function(privileges) {
'topics:create': results['topics:create'][0] || isAdminOrMod,
editable: isAdminOrMod,
view_deleted: isAdminOrMod,
- read: results.read[0] || isAdminOrMod
+ read: results.read[0] || isAdminOrMod,
+ isAdminOrMod: isAdminOrMod
}, callback);
});
};
diff --git a/src/routes/accounts.js b/src/routes/accounts.js
index 40163e095c..2d492c6c78 100644
--- a/src/routes/accounts.js
+++ b/src/routes/accounts.js
@@ -17,8 +17,11 @@ module.exports = function (app, middleware, controllers) {
setupPageRoute(app, '/user/:userslug/favourites', middleware, accountMiddlewares, controllers.accounts.posts.getFavourites);
setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.posts.getWatchedTopics);
setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.edit.get);
+ setupPageRoute(app, '/user/:userslug/edit/username', middleware, accountMiddlewares, controllers.accounts.edit.username);
+ setupPageRoute(app, '/user/:userslug/edit/email', middleware, accountMiddlewares, controllers.accounts.edit.email);
+ setupPageRoute(app, '/user/:userslug/edit/password', middleware, accountMiddlewares, controllers.accounts.edit.password);
setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.settings.get);
setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get);
- setupPageRoute(app, '/chats/:userslug?', middleware, [middleware.redirectToLoginIfGuest], controllers.accounts.chats.get);
+ setupPageRoute(app, '/chats/:userslug?', middleware, [middleware.authenticate], controllers.accounts.chats.get);
};
diff --git a/src/routes/admin.js b/src/routes/admin.js
index f60b398bb2..06edc0b805 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -80,6 +80,7 @@ function addRoutes(router, middleware, controllers) {
router.get('/advanced/post-cache', middlewares, controllers.admin.postCache.get);
router.get('/development/logger', middlewares, controllers.admin.logger.get);
+ router.get('/development/info', middlewares, controllers.admin.info.get);
}
module.exports = function(app, middleware, controllers) {
diff --git a/src/routes/api.js b/src/routes/api.js
index e0f1684f05..a888c20747 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -2,8 +2,6 @@
var express = require('express'),
- posts = require('../posts'),
- categories = require('../categories'),
uploadsController = require('../controllers/uploads');
module.exports = function(app, middleware, controllers) {
@@ -22,6 +20,7 @@ module.exports = function(app, middleware, controllers) {
router.get('/categories/:cid/moderators', controllers.api.getModerators);
router.get('/recent/posts/:term?', controllers.api.getRecentPosts);
router.get('/unread/total', middleware.authenticate, controllers.unread.unreadTotal);
+ router.get('/topic/teaser/:topic_id', controllers.topics.teaser);
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
diff --git a/src/routes/index.js b/src/routes/index.js
index 31573544d9..57a8c8134a 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -4,7 +4,6 @@ var nconf = require('nconf'),
path = require('path'),
winston = require('winston'),
controllers = require('../controllers'),
- meta = require('../meta'),
plugins = require('../plugins'),
express = require('express'),
@@ -30,14 +29,12 @@ function mainRoutes(app, middleware, controllers) {
setupPageRoute(app, '/compose', middleware, [middleware.authenticate], controllers.compose);
setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail);
setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing);
- setupPageRoute(app, '/search/:term?', middleware, [middleware.guestSearchingAllowed], controllers.search.search);
+ setupPageRoute(app, '/search/:term?', middleware, [], controllers.search.search);
setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset);
setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse);
}
function topicRoutes(app, middleware, controllers) {
- app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser);
-
setupPageRoute(app, '/topic/:topic_id/:slug/:post_index?', middleware, [], controllers.topics.get);
setupPageRoute(app, '/topic/:topic_id/:slug?', middleware, [], controllers.topics.get);
}
@@ -120,15 +117,7 @@ module.exports = function(app, middleware) {
require('./debug')(app, middleware, controllers);
}
- app.use(function(req, res, next) {
- if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) {
- return next();
- }
- if (req.path.startsWith('/uploads/files')) {
- return res.status(403).json('not-allowed');
- }
- next();
- });
+ app.use(middleware.privateUploads);
app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), {
maxAge: app.enabled('cache') ? 5184000000 : 0
@@ -144,7 +133,11 @@ module.exports = function(app, middleware) {
};
function handle404(app, middleware) {
- app.use(function(req, res, next) {
+ var relativePath = nconf.get('relative_path');
+ var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'),
+ isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
+
+ app.use(function(req, res) {
if (plugins.hasListeners('action:meta.override404')) {
return plugins.fireHook('action:meta.override404', {
req: req,
@@ -153,14 +146,14 @@ function handle404(app, middleware) {
});
}
- var relativePath = nconf.get('relative_path');
- var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'),
- isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
-
if (isClientScript.test(req.url)) {
res.type('text/javascript').status(200).send('');
} else if (isLanguage.test(req.url)) {
res.status(200).json({});
+ } else if (req.path.startsWith(relativePath + '/uploads')) {
+ res.status(404).send('');
+ } else if (req.path === '/favicon.ico') {
+ res.status(404).send('');
} else if (req.accepts('html')) {
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
@@ -182,7 +175,7 @@ function handle404(app, middleware) {
}
function handleErrors(app, middleware) {
- app.use(function(err, req, res, next) {
+ app.use(function(err, req, res) {
if (err.code === 'EBADCSRFTOKEN') {
winston.error(req.path + '\n', err.message);
return res.sendStatus(403);
diff --git a/src/search.js b/src/search.js
index ffd19b1cd6..94bc4a708f 100644
--- a/src/search.js
+++ b/src/search.js
@@ -22,15 +22,13 @@ search.search = function(data, callback) {
return callback(err);
}
- result.search_query = validator.escape(query);
+ data.search_query = validator.escape(query);
if (searchIn === 'titles' || searchIn === 'titlesposts') {
searchIn = 'posts';
}
- result[searchIn] = data.matches;
- result.matchCount = data.matchCount;
- result.pageCount = data.pageCount;
- result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2);
- callback(null, result);
+
+ data.time = (process.elapsedTimeSince(start) / 1000).toFixed(2);
+ callback(null, data);
}
var start = process.hrtime();
@@ -38,18 +36,12 @@ search.search = function(data, callback) {
var query = data.query;
var searchIn = data.searchIn || 'titlesposts';
- var result = {
- posts: [],
- users: [],
- tags: []
- };
-
if (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts') {
searchInContent(data, done);
} else if (searchIn === 'users') {
- searchInUsers(data, done);
+ user.search(data, done);
} else if (searchIn === 'tags') {
- searchInTags(query, done);
+ topics.searchAndLoadTags(data, done);
} else {
callback(new Error('[[error:unknown-search-filter]]'));
}
@@ -91,7 +83,7 @@ function searchInContent(data, callback) {
var matchCount = 0;
if (!results || (!results.pids.length && !results.tids.length)) {
- return callback(null, {matches: [], matchCount: matchCount, pageCount: 1});
+ return callback(null, {posts: [], matchCount: matchCount, pageCount: 1});
}
async.waterfall([
@@ -118,7 +110,7 @@ function searchInContent(data, callback) {
posts.getPostSummaryByPids(pids, data.uid, {}, next);
},
function(posts, next) {
- next(null, {matches: posts, matchCount: matchCount, pageCount: Math.max(1, Math.ceil(parseInt(matchCount, 10) / 10))});
+ next(null, {posts: posts, matchCount: matchCount, pageCount: Math.max(1, Math.ceil(parseInt(matchCount, 10) / 10))});
}
], callback);
});
@@ -315,16 +307,12 @@ function sortPosts(posts, data) {
}
data.sortBy = data.sortBy || 'timestamp';
data.sortDirection = data.sortDirection || 'desc';
+ var direction = data.sortDirection === 'desc' ? 1 : -1;
+
if (data.sortBy === 'timestamp') {
- if (data.sortDirection === 'desc') {
- posts.sort(function(p1, p2) {
- return p2.timestamp - p1.timestamp;
- });
- } else {
- posts.sort(function(p1, p2) {
- return p1.timestamp - p2.timestamp;
- });
- }
+ posts.sort(function(p1, p2) {
+ return direction * (p2.timestamp - p1.timestamp);
+ });
return;
}
@@ -336,21 +324,13 @@ function sortPosts(posts, data) {
return;
}
- var value = firstPost[fields[0]][fields[1]];
- var isNumeric = utils.isNumber(value);
+ var isNumeric = utils.isNumber(firstPost[fields[0]][fields[1]]);
if (isNumeric) {
- if (data.sortDirection === 'desc') {
- posts.sort(function(p1, p2) {
- return p2[fields[0]][fields[1]] - p1[fields[0]][fields[1]];
- });
- } else {
- posts.sort(function(p1, p2) {
- return p1[fields[0]][fields[1]] - p2[fields[0]][fields[1]];
- });
- }
+ posts.sort(function(p1, p2) {
+ return direction * (p2[fields[0]][fields[1]] - p1[fields[0]][fields[1]]);
+ });
} else {
- var direction = data.sortDirection === 'desc' ? 1 : -1;
posts.sort(function(p1, p2) {
if (p1[fields[0]][fields[1]] > p2[fields[0]][fields[1]]) {
return direction;
@@ -435,25 +415,6 @@ function getSearchUids(data, callback) {
}
}
-function searchInUsers(data, callback) {
- user.search(data, function(err, results) {
- if (err) {
- return callback(err);
- }
- callback(null, {matches: results.users, matchCount: results.matchCount, pageCount: results.pageCount});
- });
-}
-
-function searchInTags(query, callback) {
- topics.searchAndLoadTags({query: query}, function(err, tags) {
- if (err) {
- return callback(err);
- }
-
- callback(null, {matches: tags, matchCount: tags.length, pageCount: 1});
- });
-}
-
search.searchQuery = function(index, content, cids, uids, callback) {
plugins.fireHook('filter:search.query', {
index: index,
diff --git a/src/socket.io/admin/groups.js b/src/socket.io/admin/groups.js
index 0cd47a778f..206604ec8f 100644
--- a/src/socket.io/admin/groups.js
+++ b/src/socket.io/admin/groups.js
@@ -1,5 +1,6 @@
"use strict";
+var async = require('async');
var groups = require('../../groups'),
Groups = {};
@@ -20,7 +21,17 @@ Groups.join = function(socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
- groups.join(data.groupName, data.uid, callback);
+ async.waterfall([
+ function (next) {
+ groups.isMember(data.uid, data.groupName, next);
+ },
+ function (isMember, next) {
+ if (isMember) {
+ return next(new Error('[[error:group-already-member]]'));
+ }
+ groups.join(data.groupName, data.uid, next);
+ }
+ ], callback);
};
Groups.leave = function(socket, data, callback) {
@@ -32,7 +43,17 @@ Groups.leave = function(socket, data, callback) {
return callback(new Error('[[error:cant-remove-self-as-admin]]'));
}
- groups.leave(data.groupName, data.uid, callback);
+ async.waterfall([
+ function (next) {
+ groups.isMember(data.uid, data.groupName, next);
+ },
+ function (isMember, next) {
+ if (!isMember) {
+ return next(new Error('[[error:group-not-member]]'));
+ }
+ groups.leave(data.groupName, data.uid, next);
+ }
+ ], callback);
};
Groups.update = function(socket, data, callback) {
diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js
index 4f6e5c14c0..d43d0c8171 100644
--- a/src/socket.io/categories.js
+++ b/src/socket.io/categories.js
@@ -15,7 +15,25 @@ SocketCategories.getRecentReplies = function(socket, cid, callback) {
};
SocketCategories.get = function(socket, data, callback) {
- categories.getCategoriesByPrivilege('categories:cid', socket.uid, 'find', callback);
+ async.parallel({
+ isAdmin: async.apply(user.isAdministrator, socket.uid),
+ categories: function(next) {
+ async.waterfall([
+ async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
+ async.apply(categories.getCategoriesData),
+ ], next);
+ }
+ }, function(err, results) {
+ if (err) {
+ return callback(err);
+ }
+
+ results.categories = results.categories.filter(function(category) {
+ return category && (!category.disabled || results.isAdmin);
+ });
+
+ callback(null, results.categories);
+ });
};
SocketCategories.getWatchedCategories = function(socket, data, callback) {
diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js
index b75e6ba3df..1a07a90c26 100644
--- a/src/socket.io/groups.js
+++ b/src/socket.io/groups.js
@@ -62,8 +62,11 @@ SocketGroups.leave = function(socket, data, callback) {
function isOwner(next) {
return function (socket, data, callback) {
- groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) {
- if (err || !isOwner) {
+ async.parallel({
+ isAdmin: async.apply(user.isAdmin, socket.uid),
+ isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName)
+ }, function(err, results) {
+ if (err || (!isOwner && !results.isAdmin)) {
return callback(err || new Error('[[error:no-privileges]]'));
}
next(socket, data, callback);
diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js
index c538aca14d..5b6a2a2e67 100644
--- a/src/socket.io/helpers.js
+++ b/src/socket.io/helpers.js
@@ -3,6 +3,7 @@
var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
+var validator = require('validator');
var websockets = require('./index');
var user = require('../user');
@@ -86,14 +87,14 @@ SocketHelpers.sendNotificationToTopicOwner = function(tid, fromuid, notification
async.parallel({
username: async.apply(user.getUserField, fromuid, 'username'),
- topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug']),
+ topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug', 'title']),
}, function(err, results) {
if (err || fromuid === parseInt(results.topicData.uid, 10)) {
return;
}
notifications.create({
- bodyShort: '[[' + notification + ', ' + results.username + ']]',
+ bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicData.title + ']]',
path: nconf.get('relative_path') + '/topic/' + results.topicData.slug,
nid: 'tid:' + tid + ':uid:' + fromuid,
from: fromuid
diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index b0075c99d2..16188cb266 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -3,7 +3,6 @@
var SocketIO = require('socket.io'),
socketioWildcard = require('socketio-wildcard')(),
async = require('async'),
- fs = require('fs'),
nconf = require('nconf'),
cookieParser = require('cookie-parser')(nconf.get('secret')),
winston = require('winston'),
diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js
index 0381569472..c304833ef5 100644
--- a/src/socket.io/meta.js
+++ b/src/socket.io/meta.js
@@ -1,15 +1,10 @@
'use strict';
-var nconf = require('nconf'),
- winston = require('winston'),
- validator = require('validator'),
+var validator = require('validator'),
- db = require('../database'),
meta = require('../meta'),
user = require('../user'),
topics = require('../topics'),
- logger = require('../logger'),
- plugins = require('../plugins'),
emitter = require('../emitter'),
rooms = require('./rooms'),
@@ -52,13 +47,7 @@ SocketMeta.rooms.enter = function(socket, data, callback) {
return callback(new Error('[[error:not-allowed]]'));
}
- if (socket.currentRoom) {
- rooms.leave(socket, socket.currentRoom);
- if (socket.currentRoom.indexOf('topic') !== -1) {
- websockets.in(socket.currentRoom).emit('event:user_leave', socket.uid);
- }
- socket.currentRoom = '';
- }
+ leaveCurrentRoom(socket);
if (data.enter) {
rooms.enter(socket, data.enter);
@@ -75,6 +64,24 @@ SocketMeta.rooms.enter = function(socket, data, callback) {
callback();
};
+SocketMeta.rooms.leaveCurrent = function(socket, data, callback) {
+ if (!socket.uid || !socket.currentRoom) {
+ return callback();
+ }
+ leaveCurrentRoom(socket);
+ callback();
+};
+
+function leaveCurrentRoom(socket) {
+ if (socket.currentRoom) {
+ rooms.leave(socket, socket.currentRoom);
+ if (socket.currentRoom.indexOf('topic') !== -1) {
+ websockets.in(socket.currentRoom).emit('event:user_leave', socket.uid);
+ }
+ socket.currentRoom = '';
+ }
+}
+
SocketMeta.rooms.getAll = function(socket, data, callback) {
var roomClients = rooms.roomClients();
var socketData = {
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index b0dde46c5c..d7b2c7c03b 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -147,4 +147,11 @@ SocketModules.sounds.getMapping = function(socket, data, callback) {
meta.sounds.getMapping(callback);
};
+SocketModules.sounds.getData = function(socket, data, callback) {
+ async.parallel({
+ mapping: async.apply(meta.sounds.getMapping),
+ files: async.apply(meta.sounds.getFiles)
+ }, callback);
+};
+
module.exports = SocketModules;
diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js
index c957fff1a7..c540c338bb 100644
--- a/src/socket.io/notifications.js
+++ b/src/socket.io/notifications.js
@@ -12,6 +12,23 @@ SocketNotifs.get = function(socket, data, callback) {
}
};
+SocketNotifs.loadMore = function(socket, data, callback) {
+ if (!data || !parseInt(data.after, 10)) {
+ return callback(new Error('[[error:invalid-data]]'));
+ }
+ if (!socket.uid) {
+ return;
+ }
+ var start = parseInt(data.after, 10);
+ var stop = start + 20;
+ user.notifications.getAll(socket.uid, start, stop, function(err, notifications) {
+ if (err) {
+ return callback(err);
+ }
+ callback(null, {notifications: notifications, nextStart: stop});
+ });
+};
+
SocketNotifs.getCount = function(socket, data, callback) {
user.notifications.getUnreadCount(socket.uid, callback);
};
diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js
index 201ec350a5..3e5230db6b 100644
--- a/src/socket.io/posts/flag.js
+++ b/src/socket.io/posts/flag.js
@@ -13,17 +13,21 @@ var meta = require('../../meta');
module.exports = function(SocketPosts) {
- SocketPosts.flag = function(socket, pid, callback) {
+ SocketPosts.flag = function(socket, data, callback) {
if (!socket.uid) {
return callback(new Error('[[error:not-logged-in]]'));
}
+ if (!data || !data.pid || !data.reason) {
+ return callback(new Error('[[error:invalid-data]]'));
+ }
+
var flaggingUser = {},
post;
async.waterfall([
function (next) {
- posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next);
+ posts.getPostFields(data.pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next);
},
function (postData, next) {
if (parseInt(postData.deleted, 10) === 1) {
@@ -55,7 +59,7 @@ module.exports = function(SocketPosts) {
flaggingUser = user.userData;
flaggingUser.uid = socket.uid;
- posts.flag(post, socket.uid, next);
+ posts.flag(post, socket.uid, data.reason, next);
},
function (next) {
async.parallel({
@@ -74,8 +78,8 @@ module.exports = function(SocketPosts) {
notifications.create({
bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + post.topic.title + ']]',
bodyLong: post.content,
- pid: pid,
- nid: 'post_flag:' + pid + ':uid:' + socket.uid,
+ pid: data.pid,
+ nid: 'post_flag:' + data.pid + ':uid:' + socket.uid,
from: socket.uid
}, function(err, notification) {
if (err || !notification) {
diff --git a/src/socket.io/user.js b/src/socket.io/user.js
index 99b4299be1..08b5f11d0a 100644
--- a/src/socket.io/user.js
+++ b/src/socket.io/user.js
@@ -116,19 +116,6 @@ SocketUser.reset.commit = function(socket, data, callback) {
});
};
-
-SocketUser.isAdminOrSelf = function(socket, uid, callback) {
- if (socket.uid === parseInt(uid, 10)) {
- return callback();
- }
- user.isAdministrator(socket.uid, function(err, isAdmin) {
- if (err || !isAdmin) {
- return callback(err || new Error('[[error:no-privileges]]'));
- }
- callback();
- });
-};
-
SocketUser.follow = function(socket, data, callback) {
if (!socket.uid || !data) {
return;
@@ -182,7 +169,7 @@ SocketUser.saveSettings = function(socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
- SocketUser.isAdminOrSelf(socket, data.uid, function(err) {
+ user.isAdminOrSelf(socket.uid, data.uid, function(err) {
if (err) {
return callback(err);
}
@@ -220,6 +207,17 @@ SocketUser.getUnreadChatCount = function(socket, data, callback) {
messaging.getUnreadCount(socket.uid, callback);
};
+SocketUser.getUnreadCounts = function(socket, data, callback) {
+ if (!socket.uid) {
+ return callback(null, {});
+ }
+ async.parallel({
+ unreadTopicCount: async.apply(topics.getTotalUnread, socket.uid),
+ unreadChatCount: async.apply(messaging.getUnreadCount, socket.uid),
+ unreadNotificationCount: async.apply(user.notifications.getUnreadCount, socket.uid)
+ }, callback);
+};
+
SocketUser.loadMore = function(socket, data, callback) {
if (!data || !data.set || parseInt(data.after, 10) < 0) {
return callback(new Error('[[error:invalid-data]]'));
diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js
index f7c4d61739..d46ce9284c 100644
--- a/src/socket.io/user/picture.js
+++ b/src/socket.io/user/picture.js
@@ -23,12 +23,12 @@ module.exports = function(SocketUser) {
} else if (type === 'uploaded') {
type = 'uploadedpicture';
} else {
- return callback(new Error('[[error:invalid-image-type, ' + ['default', 'uploadedpicture'].join(', ') + ']]'));
+ return callback(new Error('[[error:invalid-image-type, ' + ['default', 'uploadedpicture'].join(', ') + ']]'));
}
async.waterfall([
function (next) {
- SocketUser.isAdminOrSelf(socket, data.uid, next);
+ user.isAdminOrSelf(socket.uid, data.uid, next);
},
function (next) {
if (!type) {
@@ -47,7 +47,7 @@ module.exports = function(SocketUser) {
return;
}
- SocketUser.isAdminOrSelf(socket, data.uid, function(err) {
+ user.isAdminOrSelf(socket.uid, data.uid, function(err) {
if (err) {
return callback(err);
}
@@ -64,7 +64,7 @@ module.exports = function(SocketUser) {
async.waterfall([
function (next) {
- SocketUser.isAdminOrSelf(socket, data.uid, next);
+ user.isAdminOrSelf(socket.uid, data.uid, next);
},
function (next) {
user.getUserField(data.uid, 'uploadedpicture', next);
diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js
index d966396a30..4e0be0acc5 100644
--- a/src/socket.io/user/profile.js
+++ b/src/socket.io/user/profile.js
@@ -8,6 +8,44 @@ var events = require('../../events');
module.exports = function(SocketUser) {
+ SocketUser.changeUsernameEmail = function(socket, data, callback) {
+ if (!data || !data.uid || !socket.uid) {
+ return callback(new Error('[[error:invalid-data]]'));
+ }
+
+ async.waterfall([
+ function (next) {
+ isAdminOrSelfAndPasswordMatch(socket.uid, data, next);
+ },
+ function (next) {
+ SocketUser.updateProfile(socket, data, next);
+ }
+ ], callback);
+ };
+
+ function isAdminOrSelfAndPasswordMatch(uid, data, callback) {
+ async.parallel({
+ isAdmin: async.apply(user.isAdministrator, uid),
+ hasPassword: async.apply(user.hasPassword, data.uid),
+ passwordMatch: async.apply(user.isPasswordCorrect, data.uid, data.password)
+ }, function(err, results) {
+ if (err) {
+ return callback(err);
+ }
+ var self = parseInt(uid, 10) === parseInt(data.uid, 10);
+
+ if (!results.isAdmin && !self) {
+ return callback(new Error('[[error:no-privileges]]'));
+ }
+
+ if (self && results.hasPassword && !results.passwordMatch) {
+ return callback(new Error('[[error:invalid-password]]'));
+ }
+
+ callback();
+ });
+ }
+
SocketUser.changePassword = function(socket, data, callback) {
if (!data || !data.uid || data.newPassword.length < meta.config.minimumPasswordLength) {
return callback(new Error('[[error:invalid-data]]'));
@@ -31,7 +69,6 @@ module.exports = function(SocketUser) {
});
};
-
SocketUser.updateProfile = function(socket, data, callback) {
if (!socket.uid) {
return callback('[[error:invalid-uid]]');
@@ -55,7 +92,7 @@ module.exports = function(SocketUser) {
if (parseInt(meta.config['username:disableEdit'], 10) === 1) {
data.username = oldUserData.username;
}
- SocketUser.isAdminOrSelf(socket, data.uid, next);
+ user.isAdminOrSelf(socket.uid, data.uid, next);
},
function (next) {
user.updateProfile(data.uid, data, next);
diff --git a/src/topics/create.js b/src/topics/create.js
index ae60a8c191..6ccc06de12 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -101,6 +101,9 @@ module.exports = function(Topics) {
check(data.tags, meta.config.minimumTagsPerTopic, meta.config.maximumTagsPerTopic, 'not-enough-tags', 'too-many-tags', next);
},
function(next) {
+ if (data.content) {
+ data.content = data.content.rtrim();
+ }
check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next);
},
function(next) {
@@ -228,7 +231,7 @@ module.exports = function(Topics) {
function(filteredData, next) {
content = filteredData.content || data.content;
if (content) {
- content = content.trim();
+ content = content.rtrim();
}
check(content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next);
diff --git a/src/topics/tags.js b/src/topics/tags.js
index fe2e8d8ea2..54514ddc42 100644
--- a/src/topics/tags.js
+++ b/src/topics/tags.js
@@ -2,12 +2,12 @@
'use strict';
var async = require('async'),
- winston = require('winston'),
+
db = require('../database'),
meta = require('../meta'),
_ = require('underscore'),
- plugins = require('../plugins'),
- utils = require('../../public/src/utils');
+ plugins = require('../plugins');
+
module.exports = function(Topics) {
@@ -248,7 +248,7 @@ module.exports = function(Topics) {
};
Topics.searchTags = function(data, callback) {
- if (!data) {
+ if (!data || !data.query) {
return callback(null, []);
}
@@ -256,9 +256,7 @@ module.exports = function(Topics) {
if (err) {
return callback(null, []);
}
- if (data.query === '') {
- return callback(null, tags);
- }
+
data.query = data.query.toLowerCase();
var matches = [];
@@ -279,8 +277,14 @@ module.exports = function(Topics) {
};
Topics.searchAndLoadTags = function(data, callback) {
+ var searchResult = {
+ tags: [],
+ matchCount: 0,
+ pageCount: 1
+ };
+
if (!data.query || !data.query.length) {
- return callback(null, []);
+ return callback(null, searchResult);
}
Topics.searchTags(data, function(err, tags) {
if (err) {
@@ -307,8 +311,10 @@ module.exports = function(Topics) {
results.tagData.sort(function(a, b) {
return b.score - a.score;
});
-
- callback(null, results.tagData);
+ searchResult.tags = results.tagData;
+ searchResult.matchCount = results.tagData.length;
+ searchResult.pageCount = 1;
+ callback(null, searchResult);
});
});
};
diff --git a/src/topics/unread.js b/src/topics/unread.js
index d70915a7b7..294ac3cf17 100644
--- a/src/topics/unread.js
+++ b/src/topics/unread.js
@@ -142,7 +142,7 @@ module.exports = function(Topics) {
if (err) {
return callback(err);
}
- require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', null, count);
+ require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', count);
callback();
});
};
diff --git a/src/user.js b/src/user.js
index 74c3a75cd6..4d6593ab20 100644
--- a/src/user.js
+++ b/src/user.js
@@ -3,14 +3,11 @@
var async = require('async'),
nconf = require('nconf'),
gravatar = require('gravatar'),
- validator = require('validator'),
plugins = require('./plugins'),
db = require('./database'),
meta = require('./meta'),
topics = require('./topics'),
- groups = require('./groups'),
- Password = require('./password'),
privileges = require('./privileges'),
utils = require('../public/src/utils');
@@ -37,6 +34,7 @@ var async = require('async'),
require('./user/approval')(User);
require('./user/invite')(User);
require('./user/icon')(User);
+ require('./user/password')(User);
User.updateLastOnlineTime = function(uid, callback) {
callback = callback || function() {};
@@ -158,7 +156,7 @@ var async = require('async'),
User.getUidByUserslug(userslug, function(err, exists) {
callback(err, !! exists);
});
- }
+ };
User.getUidByUsername = function(username, callback) {
if (!username) {
@@ -224,6 +222,18 @@ var async = require('async'),
privileges.users.isAdministrator(uid, callback);
};
+ User.isAdminOrSelf = function(callerUid, uid, callback) {
+ if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
+ return callback();
+ }
+ User.isAdministrator(callerUid, function(err, isAdmin) {
+ if (err || !isAdmin) {
+ return callback(err || new Error('[[error:no-privileges]]'));
+ }
+ callback();
+ });
+ };
+
}(exports));
diff --git a/src/user/delete.js b/src/user/delete.js
index 1eba9939a2..778d270ecc 100644
--- a/src/user/delete.js
+++ b/src/user/delete.js
@@ -4,6 +4,7 @@ var async = require('async'),
db = require('../database'),
posts = require('../posts'),
topics = require('../topics'),
+ favourites = require('../favourites'),
groups = require('../groups'),
plugins = require('../plugins'),
batch = require('../batch');
@@ -21,12 +22,35 @@ module.exports = function(User) {
function(next) {
deleteTopics(uid, next);
},
+ function(next) {
+ deleteVotes(uid, next);
+ },
function(next) {
User.deleteAccount(uid, next);
}
], callback);
};
+ function deleteVotes(uid, callback) {
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ upvotedPids: async.apply(db.getSortedSetRange, 'uid:' + uid + ':upvote', 0, -1),
+ downvotedPids: async.apply(db.getSortedSetRange, 'uid:' + uid + ':downvote', 0, -1)
+ }, next);
+ },
+ function (pids, next) {
+ pids = pids.upvotedPids.concat(pids.downvotedPids).filter(function(pid, index, array) {
+ return pid && array.indexOf(pid) === index;
+ });
+
+ async.eachLimit(pids, 50, function(pid, next) {
+ favourites.unvote(pid, uid, next);
+ }, next);
+ }
+ ], callback);
+ }
+
function deletePosts(uid, callback) {
deleteSortedSetElements('uid:' + uid + ':posts', posts.purge, callback);
}
@@ -125,20 +149,20 @@ module.exports = function(User) {
};
function deleteUserIps(uid, callback) {
- db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, function(err, ips) {
- if (err) {
- return callback(err);
+ async.waterfall([
+ function (next) {
+ db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, next);
+ },
+ function (ips, next) {
+ var keys = ips.map(function(ip) {
+ return 'ip:' + ip + ':uid';
+ });
+ db.sortedSetsRemove(keys, uid, next);
+ },
+ function (next) {
+ db.delete('uid:' + uid + ':ip', next);
}
-
- async.each(ips, function(ip, next) {
- db.sortedSetRemove('ip:' + ip + ':uid', uid, next);
- }, function(err) {
- if (err) {
- return callback(err);
- }
- db.delete('uid:' + uid + ':ip', callback);
- });
- });
+ ], callback);
}
function deleteUserFromFollowers(uid, callback) {
diff --git a/src/user/notifications.js b/src/user/notifications.js
index 1a93a6ba4b..7cc309d1a8 100644
--- a/src/user/notifications.js
+++ b/src/user/notifications.js
@@ -21,7 +21,7 @@ var async = require('async'),
if (!parseInt(uid, 10)) {
return callback(null , {read: [], unread: []});
}
- getNotifications(uid, 10, function(err, notifications) {
+ getNotifications(uid, 0, 9, function(err, notifications) {
if (err) {
return callback(err);
}
@@ -38,8 +38,8 @@ var async = require('async'),
});
};
- UserNotifications.getAll = function(uid, count, callback) {
- getNotifications(uid, count, function(err, notifs) {
+ UserNotifications.getAll = function(uid, start, stop, callback) {
+ getNotifications(uid, start, stop, function(err, notifs) {
if (err) {
return callback(err);
}
@@ -52,13 +52,13 @@ var async = require('async'),
});
};
- function getNotifications(uid, count, callback) {
+ function getNotifications(uid, start, stop, callback) {
async.parallel({
unread: function(next) {
- getNotificationsFromSet('uid:' + uid + ':notifications:unread', false, uid, 0, count - 1, next);
+ getNotificationsFromSet('uid:' + uid + ':notifications:unread', false, uid, start, stop, next);
},
read: function(next) {
- getNotificationsFromSet('uid:' + uid + ':notifications:read', true, uid, 0, count - 1, next);
+ getNotificationsFromSet('uid:' + uid + ':notifications:read', true, uid, start, stop, next);
}
}, callback);
}
diff --git a/src/user/password.js b/src/user/password.js
new file mode 100644
index 0000000000..b326313be4
--- /dev/null
+++ b/src/user/password.js
@@ -0,0 +1,34 @@
+'use strict';
+
+var nconf = require('nconf');
+
+var db = require('../database');
+var Password = require('../password');
+
+module.exports = function(User) {
+
+ User.hashPassword = function(password, callback) {
+ if (!password) {
+ return callback(null, password);
+ }
+
+ Password.hash(nconf.get('bcrypt_rounds') || 12, password, callback);
+ };
+
+ User.isPasswordCorrect = function(uid, password, callback) {
+ db.getObjectField('user:' + uid, 'password', function(err, hashedPassword) {
+ if (err || !hashedPassword) {
+ return callback(err);
+ }
+
+ Password.compare(password || '', hashedPassword, callback);
+ });
+ };
+
+ User.hasPassword = function(uid, callback) {
+ db.getObjectField('user:' + uid, 'password', function(err, hashedPassword) {
+ callback(err, !!hashedPassword);
+ });
+ };
+
+};
\ No newline at end of file
diff --git a/src/user/profile.js b/src/user/profile.js
index 0230e930a8..a516b30074 100644
--- a/src/user/profile.js
+++ b/src/user/profile.js
@@ -262,30 +262,21 @@ module.exports = function(User) {
return callback(new Error('[[user:change_password_error]]'));
}
- if(parseInt(uid, 10) !== parseInt(data.uid, 10)) {
+ if (parseInt(uid, 10) !== parseInt(data.uid, 10)) {
User.isAdministrator(uid, function(err, isAdmin) {
- if(err || !isAdmin) {
+ if (err || !isAdmin) {
return callback(err || new Error('[[user:change_password_error_privileges'));
}
hashAndSetPassword(callback);
});
} else {
- db.getObjectField('user:' + uid, 'password', function(err, currentPassword) {
- if(err) {
- return callback(err);
+ User.isPasswordCorrect(uid, data.currentPassword, function(err, correct) {
+ if (err || !correct) {
+ return callback(err || new Error('[[user:change_password_error_wrong_current]]'));
}
- if (!currentPassword) {
- return hashAndSetPassword(callback);
- }
-
- Password.compare(data.currentPassword, currentPassword, function(err, res) {
- if (err || !res) {
- return callback(err || new Error('[[user:change_password_error_wrong_current]]'));
- }
- hashAndSetPassword(callback);
- });
+ hashAndSetPassword(callback);
});
}
};
diff --git a/src/user/search.js b/src/user/search.js
index 60f125096a..ae91ba1615 100644
--- a/src/user/search.js
+++ b/src/user/search.js
@@ -45,7 +45,6 @@ module.exports = function(User) {
var pagination = User.paginate(page, uids);
uids = pagination.data;
searchResult.pagination = pagination.pagination;
- searchResult.pageCount = pagination.pageCount;
}
User.getUsers(uids, uid, next);
diff --git a/src/views/admin/advanced/database.tpl b/src/views/admin/advanced/database.tpl
index b868b322a9..4f928b1e52 100644
--- a/src/views/admin/advanced/database.tpl
+++ b/src/views/admin/advanced/database.tpl
@@ -5,6 +5,10 @@
Mongo
+
MongoDB Version {mongo.version}
+
+
Uptime in Seconds {mongo.uptime}
+
Storage Engine {mongo.storageEngine}
Collections {mongo.collections}
Objects {mongo.objects}
Avg. Object Size {mongo.avgObjSize} kb
@@ -12,7 +16,9 @@
Data Size {mongo.dataSize} mb
Storage Size {mongo.storageSize} mb
Index Size {mongo.indexSize} mb
+
File Size {mongo.fileSize} mb
+
Resident Memory {mongo.mem.resident} mb
Virtual Memory {mongo.mem.virtual} mb
diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl
new file mode 100644
index 0000000000..563d12c11f
--- /dev/null
+++ b/src/views/admin/development/info.tpl
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl
index ac6ec94515..ba7f5942e5 100644
--- a/src/views/admin/general/navigation.tpl
+++ b/src/views/admin/general/navigation.tpl
@@ -31,32 +31,32 @@
-
-
+
+
-
-
+
-
+
diff --git a/src/views/admin/header.tpl b/src/views/admin/header.tpl
index 3831f12a1c..c2828dfbd9 100644
--- a/src/views/admin/header.tpl
+++ b/src/views/admin/header.tpl
@@ -37,6 +37,7 @@
waitSeconds: 3,
urlArgs: "{cache-buster}",
paths: {
+ 'forum': '../client',
'admin': '../admin',
'vendor': '../../vendor',
'buzz': '../../vendor/buzz/buzz.min'
diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl
index b1d9eeb8af..b68e7892ac 100644
--- a/src/views/admin/manage/category.tpl
+++ b/src/views/admin/manage/category.tpl
@@ -90,7 +90,7 @@
-
+
diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl
index d3ea08f121..2b5ac35f2b 100644
--- a/src/views/admin/manage/flags.tpl
+++ b/src/views/admin/manage/flags.tpl
@@ -58,6 +58,11 @@
{posts.flags}
+
+
+
{../user.username}: "{../reason}"
+
+
diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl
index 1d0e1e3e55..99acd57b13 100644
--- a/src/views/admin/manage/group.tpl
+++ b/src/views/admin/manage/group.tpl
@@ -54,28 +54,20 @@
+
diff --git a/src/views/admin/partials/categories/category-rows.tpl b/src/views/admin/partials/categories/category-rows.tpl
index 715427c34e..3f16bedd42 100644
--- a/src/views/admin/partials/categories/category-rows.tpl
+++ b/src/views/admin/partials/categories/category-rows.tpl
@@ -16,7 +16,7 @@
-