Baris Usakli 12 years ago
commit f5619a9b29

@ -3,13 +3,14 @@
**NodeBB** is a robust Node.js driven forum built on a redis database. It is powered by web sockets, and is compatible down to IE8. **NodeBB** is a robust Node.js driven forum built on a redis database. It is powered by web sockets, and is compatible down to IE8.
* [NodeBB Homepage](http://www.nodebb.org/ "NodeBB") * [NodeBB Homepage](http://www.nodebb.org/ "NodeBB")
* [Demo & Meta Discussion](http://try.nodebb.org)
* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
* [Follow on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") * [Follow on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") * [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
![NodeBB Main Category Listing](http://i.imgur.com/ZBrHqLr.png) ![NodeBB Main Category Listing](http://i.imgur.com/zffCFoh.png)
![NodeBB Topic Page](http://i.imgur.com/YSBA6Vr.png) ![NodeBB Topic Page](http://i.imgur.com/tcHW08M.png)
## How can I follow along/contribute? ## How can I follow along/contribute?

@ -108,6 +108,9 @@
}); });
} else if (nconf.get('upgrade')) { } else if (nconf.get('upgrade')) {
nconf.file({
file: __dirname + '/config.json'
});
meta = require('./src/meta.js'); meta = require('./src/meta.js');
meta.configs.init(function () { meta.configs.init(function () {

@ -2,7 +2,7 @@
"name": "nodebb", "name": "nodebb",
"license": "GPLv3 or later", "license": "GPLv3 or later",
"description": "NodeBB Forum", "description": "NodeBB Forum",
"version": "0.0.6", "version": "0.0.7",
"homepage": "http://www.nodebb.org", "homepage": "http://www.nodebb.org",
"repository": { "repository": {
"type": "git", "type": "git",
@ -34,15 +34,15 @@
"request": "~2.25.0", "request": "~2.25.0",
"reds": "~0.2.4", "reds": "~0.2.4",
"winston": "~0.7.2", "winston": "~0.7.2",
"nodebb-plugin-mentions": "~0.1.0", "nodebb-plugin-mentions": "~0.1.9",
"nodebb-plugin-markdown": "~0.1.0", "nodebb-plugin-markdown": "~0.1.2",
"rss": "~0.2.0", "rss": "~0.2.0",
"prompt": "~0.2.11", "prompt": "~0.2.11",
"uglify-js": "~2.4.0", "uglify-js": "~2.4.0",
"validator": "~1.5.1" "validator": "~1.5.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"hiredis" : "~0.1.15" "hiredis": "~0.1.15"
}, },
"bugs": { "bugs": {
"url": "https://github.com/designcreateplay/NodeBB/issues" "url": "https://github.com/designcreateplay/NodeBB/issues"
@ -53,15 +53,22 @@
"contributors": [ "contributors": [
{ {
"name": "Andrew Rodrigues", "name": "Andrew Rodrigues",
"email": "andrew@designcreateplay.com" "email": "andrew@designcreateplay.com",
"url": "https://github.com/psychobunny"
}, },
{ {
"name": "Julian Lam", "name": "Julian Lam",
"email": "julian@designcreateplay.com" "email": "julian@designcreateplay.com",
"url": "https://github.com/julianlam"
}, },
{ {
"name": "Barış Soner Uşaklı", "name": "Barış Soner Uşaklı",
"email": "baris@designcreateplay.com" "email": "baris@designcreateplay.com",
"url": "https://github.com/barisusakli"
},
{
"name": "Andrew Darqui",
"url": "https://github.com/adarqui"
}, },
{ {
"name": "Damian Bushong", "name": "Damian Bushong",
@ -70,6 +77,10 @@
{ {
"name": "Matt Smith", "name": "Matt Smith",
"url": "https://github.com/soimafreak" "url": "https://github.com/soimafreak"
},
{
"name": "Quinton Marchi",
"url": "https://github.com/iamcardinal"
} }
] ]
} }

@ -76,6 +76,9 @@ var ajaxify = {};
templates.flush(); templates.flush();
templates.load_template(function () { templates.load_template(function () {
exec_body_scripts(content); exec_body_scripts(content);
require(['forum/' + tpl_url], function(script) {
if (script && script.init) script.init();
});
if (callback) { if (callback) {
callback(); callback();
@ -127,8 +130,11 @@ var ajaxify = {};
} }
} else if (window.location.pathname !== '/outgoing') { } else if (window.location.pathname !== '/outgoing') {
// External Link // External Link
ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
e.preventDefault(); if (config.useOutgoingLinksPage == true) {
ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
e.preventDefault();
}
} }
} }
}); });

@ -17,7 +17,7 @@ var socket,
socket.socket.connect(); socket.socket.connect();
}, 200); }, 200);
} else { } else {
socket = io.connect(config.socket.address); socket = io.connect(RELATIVE_PATH);
var reconnecting = false, var reconnecting = false,
reconnectEl, reconnectTimer; reconnectEl, reconnectTimer;

@ -1,85 +1,92 @@
(function() { define(['forum/accountheader'], function(header) {
var yourid = templates.get('yourid'), var Account = {};
theirid = templates.get('theirid'),
isFollowing = templates.get('isFollowing');
$(document).ready(function() { Account.init = function() {
var username = $('.account-username a').html(); header.init();
app.enter_room('user/' + theirid);
app.addCommasToNumbers(); var yourid = templates.get('yourid'),
theirid = templates.get('theirid'),
isFollowing = templates.get('isFollowing');
var followBtn = $('#follow-btn'); $(document).ready(function() {
var unfollowBtn = $('#unfollow-btn'); var username = $('.account-username a').html();
app.enter_room('user/' + theirid);
if (yourid !== theirid) { app.addCommasToNumbers();
if (isFollowing) {
followBtn.hide();
unfollowBtn.show();
} else {
followBtn.show();
unfollowBtn.hide();
}
} else {
followBtn.hide();
unfollowBtn.hide();
}
followBtn.on('click', function() { var followBtn = $('#follow-btn');
socket.emit('api:user.follow', { var unfollowBtn = $('#unfollow-btn');
uid: theirid
}, function(success) { if (yourid !== theirid) {
if (success) { if (isFollowing) {
followBtn.hide(); followBtn.hide();
unfollowBtn.show(); unfollowBtn.show();
app.alertSuccess('You are now following ' + username + '!');
} else { } else {
app.alertError('There was an error following' + username + '!');
}
});
return false;
});
unfollowBtn.on('click', function() {
socket.emit('api:user.unfollow', {
uid: theirid
}, function(success) {
if (success) {
followBtn.show(); followBtn.show();
unfollowBtn.hide(); unfollowBtn.hide();
app.alertSuccess('You are no longer following ' + username + '!');
} else {
app.alertError('There was an error unfollowing ' + username + '!');
} }
} else {
followBtn.hide();
unfollowBtn.hide();
}
followBtn.on('click', function() {
socket.emit('api:user.follow', {
uid: theirid
}, function(success) {
if (success) {
followBtn.hide();
unfollowBtn.show();
app.alertSuccess('You are now following ' + username + '!');
} else {
app.alertError('There was an error following' + username + '!');
}
});
return false;
}); });
return false;
});
$('.user-recent-posts .topic-row').on('click', function() { unfollowBtn.on('click', function() {
ajaxify.go($(this).attr('topic-url')); socket.emit('api:user.unfollow', {
}); uid: theirid
}, function(success) {
if (success) {
followBtn.show();
unfollowBtn.hide();
app.alertSuccess('You are no longer following ' + username + '!');
} else {
app.alertError('There was an error unfollowing ' + username + '!');
}
});
return false;
});
var onlineStatus = $('.account-online-status'); $('.user-recent-posts .topic-row').on('click', function() {
ajaxify.go($(this).attr('topic-url'));
});
function handleUserOnline(data) { socket.on('api:user.isOnline', Account.handleUserOnline);
if (data.online) {
onlineStatus.find('span span').text('online');
onlineStatus.find('i').attr('class', 'icon-circle');
} else {
onlineStatus.find('span span').text('offline');
onlineStatus.find('i').attr('class', 'icon-circle-blank');
}
}
socket.on('api:user.isOnline', handleUserOnline); socket.emit('api:user.isOnline', theirid, Account.handleUserOnline);
socket.emit('api:user.isOnline', theirid, handleUserOnline); socket.on('event:new_post', function(data) {
var html = templates.prepare(templates['account'].blocks['posts']).parse(data);
$('.user-recent-posts').prepend(html);
});
socket.on('event:new_post', function(data) {
var html = templates.prepare(templates['account'].blocks['posts']).parse(data);
$('.user-recent-posts').prepend(html);
}); });
};
}); Account.handleUserOnline = function(data) {
var onlineStatus = $('.account-online-status');
if (data.online) {
onlineStatus.find('span span').text('online');
onlineStatus.find('i').attr('class', 'icon-circle');
} else {
onlineStatus.find('span span').text('offline');
onlineStatus.find('i').attr('class', 'icon-circle-blank');
}
};
}()); return Account;
});

@ -1,88 +1,258 @@
var gravatarPicture = templates.get('gravatarpicture'); define(['forum/accountheader'], function(header) {
var uploadedPicture = templates.get('uploadedpicture'); var AccountEdit = {};
$(document).ready(function() { AccountEdit.init = function() {
header.init();
var gravatarPicture = templates.get('gravatarpicture');
var uploadedPicture = templates.get('uploadedpicture');
$('#uploadForm').submit(function() {
AccountEdit.status('uploading the file ...');
$('#uploadForm').submit(function() { $('#upload-progress-bar').css('width', '0%');
status('uploading the file ...'); $('#upload-progress-box').show();
$('#upload-progress-box').removeClass('hide');
$('#upload-progress-bar').css('width', '0%'); if (!$('#userPhotoInput').val()) {
$('#upload-progress-box').show(); AccountEdit.error('select an image to upload!');
$('#upload-progress-box').removeClass('hide'); return false;
}
$(this).find('#imageUploadCsrf').val($('#csrf_token').val());
if (!$('#userPhotoInput').val()) {
error('select an image to upload!');
return false;
}
$(this).find('#imageUploadCsrf').val($('#csrf_token').val()); $(this).ajaxSubmit({
error: function(xhr) {
AccountEdit.error('Error: ' + xhr.status);
},
$(this).ajaxSubmit({ uploadProgress: function(event, position, total, percent) {
console.log(percent);
$('#upload-progress-bar').css('width', percent + '%');
},
error: function(xhr) {
error('Error: ' + xhr.status);
},
uploadProgress: function(event, position, total, percent) { success: function(response) {
console.log(percent); if (response.error) {
$('#upload-progress-bar').css('width', percent + '%'); AccountEdit.error(response.error);
}, return;
}
var imageUrlOnServer = response.path;
success: function(response) { $('#user-current-picture').attr('src', imageUrlOnServer);
if (response.error) { $('#user-uploaded-picture').attr('src', imageUrlOnServer);
error(response.error);
return; uploadedPicture = imageUrlOnServer;
setTimeout(function() {
AccountEdit.hideAlerts();
$('#upload-picture-modal').modal('hide');
}, 750);
socket.emit('api:updateHeader', {
fields: ['username', 'picture', 'userslug']
});
AccountEdit.success('File uploaded successfully!');
} }
});
var imageUrlOnServer = response.path; return false;
});
$('#user-current-picture').attr('src', imageUrlOnServer); var selectedImageType = '';
$('#user-uploaded-picture').attr('src', imageUrlOnServer);
$('#submitBtn').on('click', function() {
var userData = {
uid: $('#inputUID').val(),
email: $('#inputEmail').val(),
fullname: $('#inputFullname').val(),
website: $('#inputWebsite').val(),
birthday: $('#inputBirthday').val(),
location: $('#inputLocation').val(),
signature: $('#inputSignature').val()
};
socket.emit('api:user.updateProfile', userData, function(err, data) {
if (data.success) {
app.alertSuccess('Your profile has been updated successfully!');
if (data.picture) {
$('#user-current-picture').attr('src', data.picture);
$('#user_label img').attr('src', data.picture);
}
if (data.gravatarpicture) {
$('#user-gravatar-picture').attr('src', data.gravatarpicture);
gravatarPicture = data.gravatarpicture;
}
} else {
app.alertError('There was an error updating your profile! ' + err.error);
}
});
return false;
});
$('#changePictureBtn').on('click', function() {
selectedImageType = '';
AccountEdit.updateImages();
uploadedPicture = imageUrlOnServer; $('#change-picture-modal').modal('show');
$('#change-picture-modal').removeClass('hide');
setTimeout(function() { return false;
hideAlerts(); });
$('#upload-picture-modal').modal('hide');
}, 750); $('#gravatar-box').on('click', function() {
$('#gravatar-box .icon-ok').show();
$('#uploaded-box .icon-ok').hide();
selectedImageType = 'gravatar';
});
socket.emit('api:updateHeader', { $('#uploaded-box').on('click', function() {
fields: ['username', 'picture', 'userslug'] $('#gravatar-box .icon-ok').hide();
}); $('#uploaded-box .icon-ok').show();
success('File uploaded successfully!'); selectedImageType = 'uploaded';
});
$('#savePictureChangesBtn').on('click', function() {
$('#change-picture-modal').modal('hide');
if (selectedImageType) {
AccountEdit.changeUserPicture(selectedImageType);
if (selectedImageType == 'gravatar')
$('#user-current-picture').attr('src', gravatarPicture);
else if (selectedImageType == 'uploaded')
$('#user-current-picture').attr('src', uploadedPicture);
} }
});
$('#upload-picture-modal').on('hide', function() {
$('#userPhotoInput').val('');
});
$('#uploadPictureBtn').on('click', function() {
$('#change-picture-modal').modal('hide');
$('#upload-picture-modal').modal('show');
$('#upload-picture-modal').removeClass('hide');
AccountEdit.hideAlerts();
return false;
});
$('#pictureUploadSubmitBtn').on('click', function() {
$('#uploadForm').submit();
}); });
return false; (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() {
passwordvalid = utils.isPasswordValid(password.val());
if (password.val().length < config.minimumPasswordLength) {
password_notify.html('Password too short');
password_notify.attr('class', 'alert alert-danger');
password_notify.removeClass('hide');
} else if (!passwordvalid) {
password_notify.html('Invalid password');
password_notify.attr('class', 'alert alert-danger');
password_notify.removeClass('hide');
} else {
password_notify.html('OK!');
password_notify.attr('class', 'alert alert-success');
password_notify.removeClass('hide');
}
onPasswordConfirmChanged();
}
function onPasswordConfirmChanged() {
if (password_notify.hasClass('alert-danger') || !password_confirm.val()) {
password_confirm_notify.addClass('hide');
return;
}
if (password.val() !== password_confirm.val()) {
password_confirm_notify.html('Passwords must match!');
password_confirm_notify.attr('class', 'alert alert-danger');
password_confirm_notify.removeClass('hide');
passwordsmatch = false;
} else {
password_confirm_notify.html('OK!');
password_confirm_notify.attr('class', 'alert alert-success');
password_confirm_notify.removeClass('hide');
passwordsmatch = true;
}
}
password.on('blur', onPasswordChanged);
password_confirm.on('blur', onPasswordConfirmChanged);
function hideAlerts() { $('#changePasswordBtn').on('click', function() {
if (passwordvalid && passwordsmatch && currentPassword.val()) {
socket.emit('api:user.changePassword', {
'currentPassword': currentPassword.val(),
'newPassword': password.val()
}, function(err) {
currentPassword.val('');
password.val('');
password_confirm.val('');
password_notify.addClass('hide');
password_confirm_notify.addClass('hide');
passwordsmatch = false;
passwordvalid = false;
if (err) {
app.alertError(err.error);
return;
}
app.alertSuccess('Your password is updated!');
});
}
return false;
});
}());
};
AccountEdit.hideAlerts = function() {
$('#alert-status').addClass('hide'); $('#alert-status').addClass('hide');
$('#alert-success').addClass('hide'); $('#alert-success').addClass('hide');
$('#alert-error').addClass('hide'); $('#alert-error').addClass('hide');
$('#upload-progress-box').addClass('hide'); $('#upload-progress-box').addClass('hide');
} }
function status(message) { AccountEdit.status = function(message) {
hideAlerts(); AccountEdit.hideAlerts();
$('#alert-status').text(message).removeClass('hide'); $('#alert-status').text(message).removeClass('hide');
} }
function success(message) { AccountEdit.success = function(message) {
hideAlerts(); AccountEdit.hideAlerts();
$('#alert-success').text(message).removeClass('hide'); $('#alert-success').text(message).removeClass('hide');
} }
function error(message) { AccountEdit.error = function(message) {
hideAlerts(); AccountEdit.hideAlerts();
$('#alert-error').text(message).removeClass('hide'); $('#alert-error').text(message).removeClass('hide');
} }
function changeUserPicture(type) { AccountEdit.changeUserPicture = function(type) {
var userData = { var userData = {
type: type type: type
}; };
@ -94,40 +264,10 @@ $(document).ready(function() {
}); });
} }
var selectedImageType = ''; AccountEdit.updateImages = function() {
$('#submitBtn').on('click', function() {
var userData = {
uid: $('#inputUID').val(),
email: $('#inputEmail').val(),
fullname: $('#inputFullname').val(),
website: $('#inputWebsite').val(),
birthday: $('#inputBirthday').val(),
location: $('#inputLocation').val(),
signature: $('#inputSignature').val()
};
socket.emit('api:user.updateProfile', userData, function(err, data) {
if (data.success) {
app.alertSuccess('Your profile has been updated successfully!');
if (data.picture) {
$('#user-current-picture').attr('src', data.picture);
$('#user_label img').attr('src', data.picture);
}
if (data.gravatarpicture) {
$('#user-gravatar-picture').attr('src', data.gravatarpicture);
gravatarPicture = data.gravatarpicture;
}
} else {
app.alertError('There was an error updating your profile! ' + err.error);
}
});
return false;
});
function updateImages() {
var currentPicture = $('#user-current-picture').attr('src'); var currentPicture = $('#user-current-picture').attr('src');
var gravatarPicture = templates.get('gravatarpicture');
var uploadedPicture = templates.get('uploadedpicture');
if (gravatarPicture) { if (gravatarPicture) {
$('#user-gravatar-picture').attr('src', gravatarPicture); $('#user-gravatar-picture').attr('src', gravatarPicture);
@ -153,139 +293,5 @@ $(document).ready(function() {
$('#uploaded-box .icon-ok').hide(); $('#uploaded-box .icon-ok').hide();
} }
return AccountEdit;
$('#changePictureBtn').on('click', function() {
selectedImageType = '';
updateImages();
$('#change-picture-modal').modal('show');
$('#change-picture-modal').removeClass('hide');
return false;
});
$('#gravatar-box').on('click', function() {
$('#gravatar-box .icon-ok').show();
$('#uploaded-box .icon-ok').hide();
selectedImageType = 'gravatar';
});
$('#uploaded-box').on('click', function() {
$('#gravatar-box .icon-ok').hide();
$('#uploaded-box .icon-ok').show();
selectedImageType = 'uploaded';
});
$('#savePictureChangesBtn').on('click', function() {
$('#change-picture-modal').modal('hide');
if (selectedImageType) {
changeUserPicture(selectedImageType);
if (selectedImageType == 'gravatar')
$('#user-current-picture').attr('src', gravatarPicture);
else if (selectedImageType == 'uploaded')
$('#user-current-picture').attr('src', uploadedPicture);
}
});
$('#upload-picture-modal').on('hide', function() {
$('#userPhotoInput').val('');
});
$('#uploadPictureBtn').on('click', function() {
$('#change-picture-modal').modal('hide');
$('#upload-picture-modal').modal('show');
$('#upload-picture-modal').removeClass('hide');
hideAlerts();
return false;
});
$('#pictureUploadSubmitBtn').on('click', function() {
$('#uploadForm').submit();
});
(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() {
passwordvalid = utils.isPasswordValid(password.val());
if (password.val().length < config.minimumPasswordLength) {
password_notify.html('Password too short');
password_notify.attr('class', 'alert alert-danger');
password_notify.removeClass('hide');
} else if (!passwordvalid) {
password_notify.html('Invalid password');
password_notify.attr('class', 'alert alert-danger');
password_notify.removeClass('hide');
} else {
password_notify.html('OK!');
password_notify.attr('class', 'alert alert-success');
password_notify.removeClass('hide');
}
onPasswordConfirmChanged();
}
function onPasswordConfirmChanged() {
if (password_notify.hasClass('alert-danger') || !password_confirm.val()) {
password_confirm_notify.addClass('hide');
return;
}
if (password.val() !== password_confirm.val()) {
password_confirm_notify.html('Passwords must match!');
password_confirm_notify.attr('class', 'alert alert-danger');
password_confirm_notify.removeClass('hide');
passwordsmatch = false;
} else {
password_confirm_notify.html('OK!');
password_confirm_notify.attr('class', 'alert alert-success');
password_confirm_notify.removeClass('hide');
passwordsmatch = true;
}
}
password.on('blur', onPasswordChanged);
password_confirm.on('blur', onPasswordConfirmChanged);
$('#changePasswordBtn').on('click', function() {
if (passwordvalid && passwordsmatch && currentPassword.val()) {
socket.emit('api:user.changePassword', {
'currentPassword': currentPassword.val(),
'newPassword': password.val()
}, function(err) {
currentPassword.val('');
password.val('');
password_confirm.val('');
password_notify.addClass('hide');
password_confirm_notify.addClass('hide');
passwordsmatch = false;
passwordvalid = false;
if (err) {
app.alertError(err.error);
return;
}
app.alertSuccess('Your password is updated!');
});
}
return false;
});
}());
}); });

@ -1,24 +1,11 @@
(function() { define(function() {
var yourid = templates.get('yourid'), var AccountHeader = {};
theirid = templates.get('theirid');
AccountHeader.init = function() {
var yourid = templates.get('yourid'),
theirid = templates.get('theirid');
function createMenu() { AccountHeader.createMenu();
var userslug = $('.account-username-box').attr('data-userslug');
var links = $('<div class="account-sub-links inline-block pull-right">\
<span id="settingsLink" class="pull-right"><a href="/user/' + userslug + '/settings">settings</a></span>\
<span id="favouritesLink" class="pull-right"><a href="/user/' + userslug + '/favourites">favourites</a></span>\
<span class="pull-right"><a href="/user/' + userslug + '/followers">followers</a></span>\
<span class="pull-right"><a href="/user/' + userslug + '/following">following</a></span>\
<span id="editLink" class="pull-right"><a href="/user/' + userslug + '/edit">edit</a></span>\
</div>');
$('.account-username-box').append(links);
}
$(document).ready(function() {
createMenu();
var editLink = $('#editLink'); var editLink = $('#editLink');
var settingsLink = $('#settingsLink'); var settingsLink = $('#settingsLink');
@ -37,6 +24,20 @@
return false; return false;
} }
}); });
}); }
AccountHeader.createMenu = function() {
var userslug = $('.account-username-box').attr('data-userslug');
var links = $('<div class="account-sub-links inline-block pull-right">\
<span id="settingsLink" class="pull-right"><a href="/user/' + userslug + '/settings">settings</a></span>\
<span id="favouritesLink" class="pull-right"><a href="/user/' + userslug + '/favourites">favourites</a></span>\
<span class="pull-right"><a href="/user/' + userslug + '/followers">followers</a></span>\
<span class="pull-right"><a href="/user/' + userslug + '/following">following</a></span>\
<span id="editLink" class="pull-right"><a href="/user/' + userslug + '/edit">edit</a></span>\
</div>');
$('.account-username-box').append(links);
}
}()); return AccountHeader;
});

@ -1,19 +1,25 @@
$(document).ready(function() { define(['forum/accountheader'], function(header) {
var AccountSettings = {};
$('#submitBtn').on('click', function() { AccountSettings.init = function() {
header.init();
var settings = { $('#submitBtn').on('click', function() {
showemail: $('#showemailCheckBox').is(':checked') ? 1 : 0
};
socket.emit('api:user.saveSettings', settings, function(success) { var settings = {
if (success) { showemail: $('#showemailCheckBox').is(':checked') ? 1 : 0
app.alertSuccess('Settings saved!'); };
} else {
app.alertError('There was an error saving settings!'); socket.emit('api:user.saveSettings', settings, function(success) {
} if (success) {
app.alertSuccess('Settings saved!');
} else {
app.alertError('There was an error saving settings!');
}
});
return false;
}); });
return false; };
});
return AccountSettings;
}); });

@ -1,139 +1,144 @@
var modified_categories = {}; define(function() {
var Categories = {};
function modified(el) { Categories.init = function() {
var cid = $(el).parents('li').attr('data-cid'); var modified_categories = {};
modified_categories[cid] = modified_categories[cid] || {}; function modified(el) {
modified_categories[cid][$(el).attr('data-name')] = $(el).val(); var cid = $(el).parents('li').attr('data-cid');
}
function save() { modified_categories[cid] = modified_categories[cid] || {};
socket.emit('api:admin.categories.update', modified_categories); modified_categories[cid][$(el).attr('data-name')] = $(el).val();
modified_categories = {};
}
function select_icon(el) {
var selected = el.attr('class').replace(' icon-2x', '');
jQuery('#icons .selected').removeClass('selected');
if (selected)
jQuery('#icons .' + selected).parent().addClass('selected');
bootbox.confirm('<h2>Select an icon.</h2>' + document.getElementById('icons').innerHTML, function(confirm) {
if (confirm) {
var iconClass = jQuery('.bootbox .selected').children(':first').attr('class');
el.attr('class', iconClass + ' icon-2x');
el.val(iconClass);
modified(el);
} }
});
setTimeout(function() { //bootbox was rewritten for BS3 and I had to add this timeout for the previous code to work. TODO: to look into
jQuery('.bootbox .col-md-3').on('click', function() {
jQuery('.bootbox .selected').removeClass('selected');
jQuery(this).addClass('selected');
});
}, 500);
}
function save() {
socket.emit('api:admin.categories.update', modified_categories);
modified_categories = {};
}
function update_blockclass(el) { function select_icon(el) {
el.parentNode.parentNode.className = 'entry-row ' + el.value; var selected = el.attr('class').replace(' icon-2x', '');
} jQuery('#icons .selected').removeClass('selected');
if (selected)
jQuery('#icons .' + selected).parent().addClass('selected');
jQuery('#entry-container').sortable();
jQuery('.blockclass').each(function() {
jQuery(this).val(this.getAttribute('data-value'));
});
bootbox.confirm('<h2>Select an icon.</h2>' + document.getElementById('icons').innerHTML, function(confirm) {
if (confirm) {
var iconClass = jQuery('.bootbox .selected').children(':first').attr('class');
el.attr('class', iconClass + ' icon-2x');
el.val(iconClass);
//DRY Failure. this needs to go into an ajaxify onready style fn. Currently is copy pasted into every single function so after ACP is off the ground fix asap modified(el);
(function() { }
function showCreateCategoryModal() { });
$('#new-category-modal').modal();
}
function createNewCategory() {
var category = {
name: $('#inputName').val(),
description: $('#inputDescription').val(),
icon: $('#new-category-modal i').attr('value'),
blockclass: $('#inputBlockclass').val()
};
socket.emit('api:admin.categories.create', category, function(err, data) {
if (!err) {
app.alert({
alert_id: 'category_created',
title: 'Created',
message: 'Category successfully created!',
type: 'success',
timeout: 2000
});
var html = templates.prepare(templates['admin/categories'].blocks['categories']).parse({ setTimeout(function() { //bootbox was rewritten for BS3 and I had to add this timeout for the previous code to work. TODO: to look into
categories: [data] jQuery('.bootbox .col-md-3').on('click', function() {
jQuery('.bootbox .selected').removeClass('selected');
jQuery(this).addClass('selected');
}); });
$('#entry-container').append(html); }, 500);
}
$('#new-category-modal').modal('hide');
}
});
}
jQuery('document').ready(function() {
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length - 1];
jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
if (this.getAttribute('href').match(active)) {
jQuery(this.parentNode).addClass('active');
return false;
}
});
jQuery('#save').on('click', save);
jQuery('#addNew').on('click', showCreateCategoryModal);
jQuery('#create-category-btn').on('click', createNewCategory);
jQuery('#entry-container').on('click', '.icon', function(ev) { function update_blockclass(el) {
select_icon($(this).find('i')); el.parentNode.parentNode.className = 'entry-row ' + el.value;
}); }
jQuery('.blockclass').on('change', function(ev) {
update_blockclass(ev.target);
});
jQuery('.category_name, .category_description, .blockclass').on('change', function(ev) { jQuery('#entry-container').sortable();
modified(ev.target); jQuery('.blockclass').each(function() {
jQuery(this).val(this.getAttribute('data-value'));
}); });
jQuery('.entry-row button').each(function(index, element) {
var disabled = $(element).attr('data-disabled');
if (disabled == "0" || disabled == "")
$(element).html('Disable');
else
$(element).html('Enable');
}); //DRY Failure. this needs to go into an ajaxify onready style fn. Currently is copy pasted into every single function so after ACP is off the ground fix asap
function showCreateCategoryModal() {
$('#new-category-modal').modal();
}
jQuery('.entry-row button').on('click', function(ev) { function createNewCategory() {
var btn = jQuery(this); var category = {
var categoryRow = btn.parents('li'); name: $('#inputName').val(),
var cid = categoryRow.attr('data-cid'); description: $('#inputDescription').val(),
icon: $('#new-category-modal i').attr('value'),
blockclass: $('#inputBlockclass').val()
};
socket.emit('api:admin.categories.create', category, function(err, data) {
if (!err) {
app.alert({
alert_id: 'category_created',
title: 'Created',
message: 'Category successfully created!',
type: 'success',
timeout: 2000
});
var html = templates.prepare(templates['admin/categories'].blocks['categories']).parse({
categories: [data]
});
$('#entry-container').append(html);
$('#new-category-modal').modal('hide');
}
});
}
var disabled = btn.html() == "Disable" ? "1" : "0"; jQuery('document').ready(function() {
categoryRow.remove(); var url = window.location.href,
modified_categories[cid] = modified_categories[cid] || {}; parts = url.split('/'),
modified_categories[cid]['disabled'] = disabled; active = parts[parts.length - 1];
jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
if (this.getAttribute('href').match(active)) {
jQuery(this.parentNode).addClass('active');
return false;
}
});
jQuery('#save').on('click', save);
jQuery('#addNew').on('click', showCreateCategoryModal);
jQuery('#create-category-btn').on('click', createNewCategory);
jQuery('#entry-container').on('click', '.icon', function(ev) {
select_icon($(this).find('i'));
});
jQuery('.blockclass').on('change', function(ev) {
update_blockclass(ev.target);
});
jQuery('.category_name, .category_description, .blockclass').on('change', function(ev) {
modified(ev.target);
});
jQuery('.entry-row button').each(function(index, element) {
var disabled = $(element).attr('data-disabled');
if (disabled == "0" || disabled == "")
$(element).html('Disable');
else
$(element).html('Enable');
});
jQuery('.entry-row button').on('click', function(ev) {
var btn = jQuery(this);
var categoryRow = btn.parents('li');
var cid = categoryRow.attr('data-cid');
var disabled = btn.html() == "Disable" ? "1" : "0";
categoryRow.remove();
modified_categories[cid] = modified_categories[cid] || {};
modified_categories[cid]['disabled'] = disabled;
save();
return false;
});
save();
return false;
}); });
};
}); return Categories;
});
}());

@ -1,121 +1,44 @@
var nodebb_admin = (function(nodebb_admin) { jQuery('document').ready(function() {
// On menu click, change "active" state
nodebb_admin.config = undefined; var menuEl = document.querySelector('.sidebar-nav'),
liEls = menuEl.querySelectorAll('li')
nodebb_admin.prepare = function() { parentEl = null;
// Come back in 500ms if the config isn't ready yet
if (nodebb_admin.config === undefined) { menuEl.addEventListener('click', function(e) {
setTimeout(function() { parentEl = e.target.parentNode;
nodebb_admin.prepare(); if (parentEl.nodeName === 'LI') {
}, 500); for (var x = 0, numLis = liEls.length; x < numLis; x++) {
return; if (liEls[x] !== parentEl) jQuery(liEls[x]).removeClass('active');
} else jQuery(parentEl).addClass('active');
// Populate the fields on the page from the config
var fields = document.querySelectorAll('#content [data-field]'),
numFields = fields.length,
saveBtn = document.getElementById('save'),
x, key, inputType;
for (x = 0; x < numFields; x++) {
key = fields[x].getAttribute('data-field');
inputType = fields[x].getAttribute('type');
if (fields[x].nodeName === 'INPUT') {
if (nodebb_admin.config[key]) {
switch (inputType) {
case 'text':
case 'textarea':
case 'number':
fields[x].value = nodebb_admin.config[key];
break;
case 'checkbox':
fields[x].checked = nodebb_admin.config[key] === '1' ? true : false;
break;
}
}
} else if (fields[x].nodeName === 'TEXTAREA') {
if (nodebb_admin.config[key]) fields[x].value = nodebb_admin.config[key];
} }
} }
}, false);
});
saveBtn.addEventListener('click', function(e) { socket.once('api:config.get', function(config) {
var key, value; require(['forum/admin/settings'], function(Settings) {
e.preventDefault(); Settings.config = config;
});
for (x = 0; x < numFields; x++) { });
key = fields[x].getAttribute('data-field');
if (fields[x].nodeName === 'INPUT') { socket.emit('api:config.get');
inputType = fields[x].getAttribute('type');
switch (inputType) { socket.on('api:config.set', function(data) {
case 'text': if (data.status === 'ok') {
case 'number': app.alert({
value = fields[x].value; alert_id: 'config_status',
break; timeout: 2500,
title: 'Changes Saved',
case 'checkbox': message: 'Your changes to the NodeBB configuration have been saved.',
value = fields[x].checked ? '1' : '0'; type: 'success'
break; });
} } else {
} else if (fields[x].nodeName === 'TEXTAREA') { app.alert({
value = fields[x].value; alert_id: 'config_status',
} timeout: 2500,
title: 'Changes Not Saved',
socket.emit('api:config.set', { message: 'NodeBB encountered a problem saving your changes',
key: key, type: 'danger'
value: value
});
}
}); });
} }
});
nodebb_admin.remove = function(key) {
socket.emit('api:config.remove', key);
}
jQuery('document').ready(function() {
// On menu click, change "active" state
var menuEl = document.querySelector('.sidebar-nav'),
liEls = menuEl.querySelectorAll('li')
parentEl = null;
menuEl.addEventListener('click', function(e) {
parentEl = e.target.parentNode;
if (parentEl.nodeName === 'LI') {
for (var x = 0, numLis = liEls.length; x < numLis; x++) {
if (liEls[x] !== parentEl) jQuery(liEls[x]).removeClass('active');
else jQuery(parentEl).addClass('active');
}
}
}, false);
});
socket.once('api:config.get', function(config) {
nodebb_admin.config = config;
});
socket.emit('api:config.get');
socket.on('api:config.set', function(data) {
if (data.status === 'ok') {
app.alert({
alert_id: 'config_status',
timeout: 2500,
title: 'Changes Saved',
message: 'Your changes to the NodeBB configuration have been saved.',
type: 'success'
});
} else {
app.alert({
alert_id: 'config_status',
timeout: 2500,
title: 'Changes Not Saved',
message: 'NodeBB encountered a problem saving your changes',
type: 'danger'
});
}
});
return nodebb_admin;
}(nodebb_admin || {}));

@ -1,194 +1,200 @@
$(document).ready(function() { define(function() {
var createEl = document.getElementById('create'), var Groups = {};
createModal = $('#create-modal'),
createSubmitBtn = document.getElementById('create-modal-go'), Groups.init = function() {
createNameEl = $('#create-group-name'), var createEl = document.getElementById('create'),
detailsModal = $('#group-details-modal'), createModal = $('#create-modal'),
detailsSearch = detailsModal.find('#group-details-search'), createSubmitBtn = document.getElementById('create-modal-go'),
searchResults = detailsModal.find('#group-details-search-results'), createNameEl = $('#create-group-name'),
groupMembersEl = detailsModal.find('ul.current_members'), detailsModal = $('#group-details-modal'),
detailsModalSave = detailsModal.find('.btn-primary'), detailsSearch = detailsModal.find('#group-details-search'),
searchDelay = undefined, searchResults = detailsModal.find('#group-details-search-results'),
listEl = $('#groups-list'); groupMembersEl = detailsModal.find('ul.current_members'),
detailsModalSave = detailsModal.find('.btn-primary'),
createEl.addEventListener('click', function() { searchDelay = undefined,
createModal.modal('show'); listEl = $('#groups-list');
setTimeout(function() {
createNameEl.focus(); createEl.addEventListener('click', function() {
}, 250); createModal.modal('show');
}, false); setTimeout(function() {
createNameEl.focus();
createSubmitBtn.addEventListener('click', function() { }, 250);
var submitObj = { }, false);
name: createNameEl.val(),
description: $('#create-group-desc').val() createSubmitBtn.addEventListener('click', function() {
}, var submitObj = {
errorEl = $('#create-modal-error'), name: createNameEl.val(),
errorText; description: $('#create-group-desc').val()
},
socket.emit('api:groups.create', submitObj, function(err, data) { errorEl = $('#create-modal-error'),
if (err) { errorText;
switch (err) {
case 'group-exists': socket.emit('api:groups.create', submitObj, function(err, data) {
errorText = '<strong>Please choose another name</strong><p>There seems to be a group with this name already.</p>'; if (err) {
break; switch (err) {
case 'name-too-short': case 'group-exists':
errorText = '<strong>Please specify a grou name</strong><p>A group name is required for administrative purposes.</p>'; errorText = '<strong>Please choose another name</strong><p>There seems to be a group with this name already.</p>';
break; break;
default: case 'name-too-short':
errorText = '<strong>Uh-Oh</strong><p>There was a problem creating your group. Please try again later!</p>'; errorText = '<strong>Please specify a grou name</strong><p>A group name is required for administrative purposes.</p>';
break; break;
default:
errorText = '<strong>Uh-Oh</strong><p>There was a problem creating your group. Please try again later!</p>';
break;
}
errorEl.html(errorText).removeClass('hide');
} else {
createModal.modal('hide');
errorEl.addClass('hide');
createNameEl.val('');
ajaxify.go('admin/groups');
} }
});
});
errorEl.html(errorText).removeClass('hide'); listEl.on('click', 'button[data-action]', function() {
} else { var action = this.getAttribute('data-action'),
createModal.modal('hide'); gid = $(this).parents('li[data-gid]').attr('data-gid');
errorEl.addClass('hide');
createNameEl.val(''); switch (action) {
ajaxify.go('admin/groups'); case 'delete':
bootbox.confirm('Are you sure you wish to delete this group?', function(confirm) {
if (confirm) {
socket.emit('api:groups.delete', gid, function(err, data) {
if (data === 'OK') ajaxify.go('admin/groups');
});
}
});
break;
case 'members':
socket.emit('api:groups.get', gid, function(err, groupObj) {
var formEl = detailsModal.find('form'),
nameEl = formEl.find('#change-group-name'),
descEl = formEl.find('#change-group-desc'),
memberIcon = document.createElement('li'),
numMembers = groupObj.members.length,
membersFrag = document.createDocumentFragment(),
memberIconImg, x;
nameEl.val(groupObj.name);
descEl.val(groupObj.description);
// Member list
memberIcon.innerHTML = '<img /><span></span>';
memberIconImg = memberIcon.querySelector('img');
memberIconLabel = memberIcon.querySelector('span');
if (numMembers > 0) {
for (x = 0; x < numMembers; x++) {
memberIconImg.src = groupObj.members[x].picture;
memberIconLabel.innerHTML = groupObj.members[x].username;
memberIcon.setAttribute('data-uid', groupObj.members[x].uid);
membersFrag.appendChild(memberIcon.cloneNode(true));
}
groupMembersEl.html('');
groupMembersEl[0].appendChild(membersFrag);
}
detailsModal.attr('data-gid', groupObj.gid);
detailsModal.modal('show');
});
break;
} }
}); });
});
detailsSearch.on('keyup', function() {
listEl.on('click', 'button[data-action]', function() { var searchEl = this;
var action = this.getAttribute('data-action'),
gid = $(this).parents('li[data-gid]').attr('data-gid'); if (searchDelay) clearTimeout(searchDelay);
switch (action) { searchDelay = setTimeout(function() {
case 'delete': var searchText = searchEl.value,
bootbox.confirm('Are you sure you wish to delete this group?', function(confirm) { resultsEl = document.getElementById('group-details-search-results'),
if (confirm) { foundUser = document.createElement('li'),
socket.emit('api:groups.delete', gid, function(err, data) { foundUserImg, foundUserLabel;
if (data === 'OK') ajaxify.go('admin/groups');
}); foundUser.innerHTML = '<img /><span></span>';
} foundUserImg = foundUser.getElementsByTagName('img')[0];
}); foundUserLabel = foundUser.getElementsByTagName('span')[0];
break;
case 'members': socket.emit('api:admin.user.search', searchText, function(err, results) {
socket.emit('api:groups.get', gid, function(err, groupObj) { if (!err && results && results.length > 0) {
var formEl = detailsModal.find('form'), var numResults = results.length,
nameEl = formEl.find('#change-group-name'), resultsSlug = document.createDocumentFragment(),
descEl = formEl.find('#change-group-desc'), x;
memberIcon = document.createElement('li'), if (numResults > 4) numResults = 4;
numMembers = groupObj.members.length, for (x = 0; x < numResults; x++) {
membersFrag = document.createDocumentFragment(), foundUserImg.src = results[x].picture;
memberIconImg, x; foundUserLabel.innerHTML = results[x].username;
foundUser.setAttribute('title', results[x].username);
foundUser.setAttribute('data-uid', results[x].uid);
nameEl.val(groupObj.name); resultsSlug.appendChild(foundUser.cloneNode(true));
descEl.val(groupObj.description);
// Member list
memberIcon.innerHTML = '<img /><span></span>';
memberIconImg = memberIcon.querySelector('img');
memberIconLabel = memberIcon.querySelector('span');
if (numMembers > 0) {
for (x = 0; x < numMembers; x++) {
memberIconImg.src = groupObj.members[x].picture;
memberIconLabel.innerHTML = groupObj.members[x].username;
memberIcon.setAttribute('data-uid', groupObj.members[x].uid);
membersFrag.appendChild(memberIcon.cloneNode(true));
} }
groupMembersEl.html('');
groupMembersEl[0].appendChild(membersFrag);
}
detailsModal.attr('data-gid', groupObj.gid); resultsEl.innerHTML = '';
detailsModal.modal('show'); resultsEl.appendChild(resultsSlug);
} else resultsEl.innerHTML = '<li>No Users Found</li>';
}); });
break; }, 200);
} });
});
detailsSearch.on('keyup', function() {
var searchEl = this;
if (searchDelay) clearTimeout(searchDelay);
searchDelay = setTimeout(function() {
var searchText = searchEl.value,
resultsEl = document.getElementById('group-details-search-results'),
foundUser = document.createElement('li'),
foundUserImg, foundUserLabel;
foundUser.innerHTML = '<img /><span></span>';
foundUserImg = foundUser.getElementsByTagName('img')[0];
foundUserLabel = foundUser.getElementsByTagName('span')[0];
socket.emit('api:admin.user.search', searchText, function(err, results) {
if (!err && results && results.length > 0) {
var numResults = results.length,
resultsSlug = document.createDocumentFragment(),
x;
if (numResults > 4) numResults = 4;
for (x = 0; x < numResults; x++) {
foundUserImg.src = results[x].picture;
foundUserLabel.innerHTML = results[x].username;
foundUser.setAttribute('title', results[x].username);
foundUser.setAttribute('data-uid', results[x].uid);
resultsSlug.appendChild(foundUser.cloneNode(true));
}
resultsEl.innerHTML = ''; searchResults.on('click', 'li[data-uid]', function() {
resultsEl.appendChild(resultsSlug); var userLabel = this,
} else resultsEl.innerHTML = '<li>No Users Found</li>'; uid = parseInt(this.getAttribute('data-uid')),
}); gid = detailsModal.attr('data-gid'),
}, 200); members = [];
});
searchResults.on('click', 'li[data-uid]', function() { groupMembersEl.find('li[data-uid]').each(function() {
var userLabel = this, members.push(parseInt(this.getAttribute('data-uid')));
uid = parseInt(this.getAttribute('data-uid')), });
gid = detailsModal.attr('data-gid'),
members = [];
groupMembersEl.find('li[data-uid]').each(function() { if (members.indexOf(uid) === -1) {
members.push(parseInt(this.getAttribute('data-uid'))); socket.emit('api:groups.join', {
gid: gid,
uid: uid
}, function(err, data) {
if (!err) {
groupMembersEl.append(userLabel.cloneNode(true));
}
});
}
}); });
if (members.indexOf(uid) === -1) { groupMembersEl.on('click', 'li[data-uid]', function() {
socket.emit('api:groups.join', { var uid = this.getAttribute('data-uid'),
gid = detailsModal.attr('data-gid');
socket.emit('api:groups.leave', {
gid: gid, gid: gid,
uid: uid uid: uid
}, function(err, data) { }, function(err, data) {
if (!err) { if (!err) {
groupMembersEl.append(userLabel.cloneNode(true)); groupMembersEl.find('li[data-uid="' + uid + '"]').remove();
} }
}); });
}
});
groupMembersEl.on('click', 'li[data-uid]', function() {
var uid = this.getAttribute('data-uid'),
gid = detailsModal.attr('data-gid');
socket.emit('api:groups.leave', {
gid: gid,
uid: uid
}, function(err, data) {
if (!err) {
groupMembersEl.find('li[data-uid="' + uid + '"]').remove();
}
}); });
});
detailsModalSave.on('click', function() {
detailsModalSave.on('click', function() { var formEl = detailsModal.find('form'),
var formEl = detailsModal.find('form'), nameEl = formEl.find('#change-group-name'),
nameEl = formEl.find('#change-group-name'), descEl = formEl.find('#change-group-desc'),
descEl = formEl.find('#change-group-desc'), gid = detailsModal.attr('data-gid');
gid = detailsModal.attr('data-gid');
socket.emit('api:groups.update', {
socket.emit('api:groups.update', { gid: gid,
gid: gid, values: {
values: { name: nameEl.val(),
name: nameEl.val(), description: descEl.val()
description: descEl.val() }
} }, function(err) {
}, function(err) { if (!err) {
if (!err) { detailsModal.modal('hide');
detailsModal.modal('hide'); ajaxify.go('admin/groups');
ajaxify.go('admin/groups'); }
} });
}); });
}); };
return Groups;
}); });

@ -1,25 +1,28 @@
define(function() {
var Admin = {};
(function() { Admin.init = function() {
ajaxify.register_events(['api:get_all_rooms']);
socket.on('api:get_all_rooms', function(data) {
ajaxify.register_events(['api:get_all_rooms']); var active_users = document.getElementById('active_users'),
socket.on('api:get_all_rooms', function(data) { total = 0;
active_users.innerHTML = '';
var active_users = document.getElementById('active_users'), for (var room in data) {
total = 0; if (room !== '') {
active_users.innerHTML = ''; var count = data[room].length;
total += count;
for (var room in data) { active_users.innerHTML = active_users.innerHTML + "<div class='alert alert-success'><strong>" + room + "</strong> " + count + " active user" + (count > 1 ? "s" : "") + "</div>";
if (room !== '') { }
var count = data[room].length;
total += count;
active_users.innerHTML = active_users.innerHTML + "<div class='alert alert-success'><strong>" + room + "</strong> " + count + " active user" + (count > 1 ? "s" : "") + "</div>";
} }
}
document.getElementById('connections').innerHTML = total; document.getElementById('connections').innerHTML = total;
}); });
app.enter_room('admin'); app.enter_room('admin');
socket.emit('api:get_all_rooms'); socket.emit('api:get_all_rooms');
};
}()); return Admin;
});

@ -1,7 +1,5 @@
var nodebb_admin = nodebb_admin || {}; define(function() {
var Plugins = {
(function() {
var plugins = {
init: function() { init: function() {
var pluginsList = $('.plugins'), var pluginsList = $('.plugins'),
numPlugins = pluginsList[0].querySelectorAll('li').length, numPlugins = pluginsList[0].querySelectorAll('li').length,
@ -31,8 +29,5 @@ var nodebb_admin = nodebb_admin || {};
} }
}; };
jQuery(document).ready(function() { return Plugins;
nodebb_admin.plugins = plugins; });
nodebb_admin.plugins.init();
});
})();

@ -0,0 +1,81 @@
define(function() {
var Settings = {};
Settings.config = {};
Settings.init = function() {
Settings.prepare();
};
Settings.prepare = function() {
// Come back in 500ms if the config isn't ready yet
if (Settings.config === undefined) {
setTimeout(function() {
Settings.prepare();
}, 500);
return;
}
// Populate the fields on the page from the config
var fields = document.querySelectorAll('#content [data-field]'),
numFields = fields.length,
saveBtn = document.getElementById('save'),
x, key, inputType;
for (x = 0; x < numFields; x++) {
key = fields[x].getAttribute('data-field');
inputType = fields[x].getAttribute('type');
if (fields[x].nodeName === 'INPUT') {
if (Settings.config[key]) {
switch (inputType) {
case 'text':
case 'textarea':
case 'number':
fields[x].value = Settings.config[key];
break;
case 'checkbox':
fields[x].checked = Settings.config[key] === '1' ? true : false;
break;
}
}
} else if (fields[x].nodeName === 'TEXTAREA') {
if (Settings.config[key]) fields[x].value = Settings.config[key];
}
}
saveBtn.addEventListener('click', function(e) {
var key, value;
e.preventDefault();
for (x = 0; x < numFields; x++) {
key = fields[x].getAttribute('data-field');
if (fields[x].nodeName === 'INPUT') {
inputType = fields[x].getAttribute('type');
switch (inputType) {
case 'text':
case 'number':
value = fields[x].value;
break;
case 'checkbox':
value = fields[x].checked ? '1' : '0';
break;
}
} else if (fields[x].nodeName === 'TEXTAREA') {
value = fields[x].value;
}
socket.emit('api:config.set', {
key: key,
value: value
});
}
});
};
Settings.remove = function(key) {
socket.emit('api:config.remove', key);
};
return Settings;
});

@ -1,8 +1,91 @@
var nodebb_admin = (function(nodebb_admin) { define(function() {
var Themes = {};
var themes = {}; Themes.init = function() {
var scriptEl = document.createElement('script');
scriptEl.src = 'http://api.bootswatch.com/3/?callback=bootswatchListener';
document.body.appendChild(scriptEl);
themes.render = function(bootswatch) { var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
installedThemeContainer = document.querySelector('#installed_themes'),
themeEvent = function(e) {
if (e.target.hasAttribute('data-action')) {
switch (e.target.getAttribute('data-action')) {
case 'preview':
var cssSrc = $(e.target).parents('li').attr('data-css'),
cssEl = document.getElementById('base-theme');
cssEl.href = cssSrc;
break;
case 'use':
var parentEl = $(e.target).parents('li'),
cssSrc = parentEl.attr('data-css'),
cssName = parentEl.attr('data-theme');
socket.emit('api:config.set', {
key: 'theme:id',
value: 'bootswatch:' + cssName
});
socket.emit('api:config.set', {
key: 'theme:src',
value: cssSrc
});
break;
}
}
};
bootstrapThemeContainer.addEventListener('click', themeEvent);
installedThemeContainer.addEventListener('click', themeEvent);
var revertEl = document.getElementById('revert_theme');
revertEl.addEventListener('click', function() {
bootbox.confirm('Are you sure you wish to remove the custom theme and restore the NodeBB default theme?', function(confirm) {
if (confirm) {
require(['forum/admin/settings'], function(Settings) {
Settings.remove('theme:id');
Settings.remove('theme:src');
});
}
});
}, false);
// Installed Themes
socket.emit('api:admin.themes.getInstalled', function(themes) {
var instListEl = document.getElementById('installed_themes'),
themeFrag = document.createDocumentFragment(),
liEl = document.createElement('li');
if (themes.length > 0) {
for (var x = 0, numThemes = themes.length; x < numThemes; x++) {
liEl.setAttribute('data-theme', themes[x].id);
liEl.setAttribute('data-css', themes[x].src);
liEl.innerHTML = '<img src="' + themes[x].screenshot + '" />' +
'<div>' +
'<div class="pull-right">' +
'<button class="btn btn-primary" data-action="use">Use</button> ' +
'<button class="btn btn-default" data-action="preview">Preview</button>' +
'</div>' +
'<h4>' + themes[x].name + '</h4>' +
'<p>' +
themes[x].description +
(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
'</p>' +
'</div>' +
'<div class="clear">';
themeFrag.appendChild(liEl.cloneNode(true));
}
} else {
// No themes found
liEl.className = 'no-themes';
liEl.innerHTML = 'No installed themes found';
themeFrag.appendChild(liEl);
}
instListEl.innerHTML = '';
instListEl.appendChild(themeFrag);
});
}
Themes.render = function(bootswatch) {
var themeFrag = document.createDocumentFragment(), var themeFrag = document.createDocumentFragment(),
themeEl = document.createElement('li'), themeEl = document.createElement('li'),
themeContainer = document.querySelector('#bootstrap_themes'), themeContainer = document.querySelector('#bootstrap_themes'),
@ -28,91 +111,5 @@ var nodebb_admin = (function(nodebb_admin) {
themeContainer.appendChild(themeFrag); themeContainer.appendChild(themeFrag);
} }
nodebb_admin.themes = themes; return Themes;
});
return nodebb_admin;
}(nodebb_admin || {}));
(function() {
var scriptEl = document.createElement('script');
scriptEl.src = 'http://api.bootswatch.com/3/?callback=nodebb_admin.themes.render';
document.body.appendChild(scriptEl);
var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
installedThemeContainer = document.querySelector('#installed_themes'),
themeEvent = function(e) {
if (e.target.hasAttribute('data-action')) {
switch (e.target.getAttribute('data-action')) {
case 'preview':
var cssSrc = $(e.target).parents('li').attr('data-css'),
cssEl = document.getElementById('base-theme');
cssEl.href = cssSrc;
break;
case 'use':
var parentEl = $(e.target).parents('li'),
cssSrc = parentEl.attr('data-css'),
cssName = parentEl.attr('data-theme');
socket.emit('api:config.set', {
key: 'theme:id',
value: 'bootswatch:' + cssName
});
socket.emit('api:config.set', {
key: 'theme:src',
value: cssSrc
});
break;
}
}
};
bootstrapThemeContainer.addEventListener('click', themeEvent);
installedThemeContainer.addEventListener('click', themeEvent);
var revertEl = document.getElementById('revert_theme');
revertEl.addEventListener('click', function() {
bootbox.confirm('Are you sure you wish to remove the custom theme and restore the NodeBB default theme?', function(confirm) {
if (confirm) {
nodebb_admin.remove('theme:id');
nodebb_admin.remove('theme:src');
}
});
}, false);
// Installed Themes
socket.emit('api:admin.themes.getInstalled', function(themes) {
var instListEl = document.getElementById('installed_themes'),
themeFrag = document.createDocumentFragment(),
liEl = document.createElement('li');
if (themes.length > 0) {
for (var x = 0, numThemes = themes.length; x < numThemes; x++) {
liEl.setAttribute('data-theme', themes[x].id);
liEl.setAttribute('data-css', themes[x].src);
liEl.innerHTML = '<img src="' + themes[x].screenshot + '" />' +
'<div>' +
'<div class="pull-right">' +
'<button class="btn btn-primary" data-action="use">Use</button> ' +
'<button class="btn btn-default" data-action="preview">Preview</button>' +
'</div>' +
'<h4>' + themes[x].name + '</h4>' +
'<p>' +
themes[x].description +
(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
'</p>' +
'</div>' +
'<div class="clear">';
themeFrag.appendChild(liEl.cloneNode(true));
}
} else {
// No themes found
liEl.className = 'no-themes';
liEl.innerHTML = 'No installed themes found';
themeFrag.appendChild(liEl);
}
instListEl.innerHTML = '';
instListEl.appendChild(themeFrag);
});
})();

@ -1,127 +1,133 @@
$(document).ready(function() { define(function() {
var topicsListEl = document.querySelector('.topics'), var Topics = {};
loadMoreEl = document.getElementById('topics_loadmore');
Topics.init = function() {
$(topicsListEl).on('click', '[data-action]', function() { var topicsListEl = document.querySelector('.topics'),
var $this = $(this), loadMoreEl = document.getElementById('topics_loadmore');
action = this.getAttribute('data-action'),
tid = $this.parents('[data-tid]').attr('data-tid'); $(topicsListEl).on('click', '[data-action]', function() {
var $this = $(this),
switch (action) { action = this.getAttribute('data-action'),
case 'pin': tid = $this.parents('[data-tid]').attr('data-tid');
if (!$this.hasClass('active')) socket.emit('api:topic.pin', {
tid: tid switch (action) {
case 'pin':
if (!$this.hasClass('active')) socket.emit('api:topic.pin', {
tid: tid
});
else socket.emit('api:topic.unpin', {
tid: tid
});
break;
case 'lock':
if (!$this.hasClass('active')) socket.emit('api:topic.lock', {
tid: tid
});
else socket.emit('api:topic.unlock', {
tid: tid
});
break;
case 'delete':
if (!$this.hasClass('active')) socket.emit('api:topic.delete', {
tid: tid
});
else socket.emit('api:topic.restore', {
tid: tid
});
break;
}
});
loadMoreEl.addEventListener('click', function() {
if (this.className.indexOf('disabled') === -1) {
var topics = document.querySelectorAll('.topics li[data-tid]'),
lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
this.innerHTML = '<i class="icon-refresh icon-spin"></i> Retrieving topics';
socket.emit('api:admin.topics.getMore', {
limit: 10,
after: lastTid
}, function(topics) {
var btnEl = document.getElementById('topics_loadmore');
topics = JSON.parse(topics);
if (topics.length > 0) {
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
topics: topics
}),
topicsListEl = document.querySelector('.topics');
topicsListEl.innerHTML += html;
btnEl.innerHTML = 'Load More Topics';
} else {
// Exhausted all topics
btnEl.className += ' disabled';
btnEl.innerHTML = 'No more topics';
}
}); });
else socket.emit('api:topic.unpin', { }
tid: tid }, false);
});
break; // Resolve proper button state for all topics
case 'lock': var topicEls = topicsListEl.querySelectorAll('li'),
if (!$this.hasClass('active')) socket.emit('api:topic.lock', { numTopics = topicEls.length;
tid: tid for (var x = 0; x < numTopics; x++) {
}); if (topicEls[x].getAttribute('data-pinned') === '1') topicEls[x].querySelector('[data-action="pin"]').className += ' active';
else socket.emit('api:topic.unlock', { if (topicEls[x].getAttribute('data-locked') === '1') topicEls[x].querySelector('[data-action="lock"]').className += ' active';
tid: tid if (topicEls[x].getAttribute('data-deleted') === '1') topicEls[x].querySelector('[data-action="delete"]').className += ' active';
}); topicEls[x].removeAttribute('data-pinned');
break; topicEls[x].removeAttribute('data-locked');
case 'delete': topicEls[x].removeAttribute('data-deleted');
if (!$this.hasClass('active')) socket.emit('api:topic.delete', {
tid: tid
});
else socket.emit('api:topic.restore', {
tid: tid
});
break;
}
});
loadMoreEl.addEventListener('click', function() {
if (this.className.indexOf('disabled') === -1) {
var topics = document.querySelectorAll('.topics li[data-tid]'),
lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
this.innerHTML = '<i class="icon-refresh icon-spin"></i> Retrieving topics';
socket.emit('api:admin.topics.getMore', {
limit: 10,
after: lastTid
}, function(topics) {
var btnEl = document.getElementById('topics_loadmore');
topics = JSON.parse(topics);
if (topics.length > 0) {
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
topics: topics
}),
topicsListEl = document.querySelector('.topics');
topicsListEl.innerHTML += html;
btnEl.innerHTML = 'Load More Topics';
} else {
// Exhausted all topics
btnEl.className += ' disabled';
btnEl.innerHTML = 'No more topics';
}
});
} }
}, false);
// Resolve proper button state for all topics
var topicEls = topicsListEl.querySelectorAll('li'),
numTopics = topicEls.length;
for (var x = 0; x < numTopics; x++) {
if (topicEls[x].getAttribute('data-pinned') === '1') topicEls[x].querySelector('[data-action="pin"]').className += ' active';
if (topicEls[x].getAttribute('data-locked') === '1') topicEls[x].querySelector('[data-action="lock"]').className += ' active';
if (topicEls[x].getAttribute('data-deleted') === '1') topicEls[x].querySelector('[data-action="delete"]').className += ' active';
topicEls[x].removeAttribute('data-pinned');
topicEls[x].removeAttribute('data-locked');
topicEls[x].removeAttribute('data-deleted');
}
});
socket.on('api:topic.pin', function(response) { socket.on('api:topic.pin', function(response) {
if (response.status === 'ok') { if (response.status === 'ok') {
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="pin"]'); var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="pin"]');
$(btnEl).addClass('active'); $(btnEl).addClass('active');
} }
}); });
socket.on('api:topic.unpin', function(response) { socket.on('api:topic.unpin', function(response) {
if (response.status === 'ok') { if (response.status === 'ok') {
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="pin"]'); var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="pin"]');
$(btnEl).removeClass('active'); $(btnEl).removeClass('active');
} }
}); });
socket.on('api:topic.lock', function(response) { socket.on('api:topic.lock', function(response) {
if (response.status === 'ok') { if (response.status === 'ok') {
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="lock"]'); var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="lock"]');
$(btnEl).addClass('active'); $(btnEl).addClass('active');
} }
}); });
socket.on('api:topic.unlock', function(response) { socket.on('api:topic.unlock', function(response) {
if (response.status === 'ok') { if (response.status === 'ok') {
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="lock"]'); var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="lock"]');
$(btnEl).removeClass('active'); $(btnEl).removeClass('active');
} }
}); });
socket.on('api:topic.delete', function(response) { socket.on('api:topic.delete', function(response) {
if (response.status === 'ok') { if (response.status === 'ok') {
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="delete"]'); var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="delete"]');
$(btnEl).addClass('active'); $(btnEl).addClass('active');
} }
}); });
socket.on('api:topic.restore', function(response) {
if (response.status === 'ok') {
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="delete"]');
socket.on('api:topic.restore', function(response) { $(btnEl).removeClass('active');
if (response.status === 'ok') { }
var btnEl = document.querySelector('li[data-tid="' + response.tid + '"] button[data-action="delete"]'); });
};
$(btnEl).removeClass('active'); return Topics;
}
}); });

@ -1,170 +1,174 @@
(function() { define(function() {
var Users = {};
var yourid = templates.get('yourid');
function isUserAdmin(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-admin') !== "0");
}
function isUserBanned(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-banned') !== "" && parent.attr('data-banned') !== "0");
}
function getUID(element) {
var parent = $(element).parents('.users-box');
return parent.attr('data-uid');
}
function updateUserButtons() {
jQuery('.ban-btn').each(function(index, element) {
var banBtn = $(element);
var uid = getUID(banBtn);
if (isUserAdmin(banBtn) || uid === yourid)
banBtn.addClass('disabled');
else if (isUserBanned(banBtn))
banBtn.addClass('btn-warning');
else
banBtn.removeClass('btn-warning');
}); Users.init = function() {
} var yourid = templates.get('yourid');
function initUsers() { function isUserAdmin(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-admin') !== "0");
}
updateUserButtons(); function isUserBanned(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-banned') !== "" && parent.attr('data-banned') !== "0");
}
$('#users-container').on('click', '.ban-btn', function() { function getUID(element) {
var banBtn = $(this); var parent = $(element).parents('.users-box');
var isAdmin = isUserAdmin(banBtn); return parent.attr('data-uid');
var isBanned = isUserBanned(banBtn); }
var parent = banBtn.parents('.users-box');
var uid = getUID(banBtn);
if (!isAdmin) { function updateUserButtons() {
if (isBanned) { jQuery('.ban-btn').each(function(index, element) {
socket.emit('api:admin.user.unbanUser', uid); var banBtn = $(element);
var uid = getUID(banBtn);
if (isUserAdmin(banBtn) || uid === yourid)
banBtn.addClass('disabled');
else if (isUserBanned(banBtn))
banBtn.addClass('btn-warning');
else
banBtn.removeClass('btn-warning'); banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('api:admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
}
});
}
}
return false;
});
}
});
}
jQuery('document').ready(function() { function initUsers() {
var timeoutId = 0, updateUserButtons();
loadingMoreUsers = false;
var url = window.location.href, $('#users-container').on('click', '.ban-btn', function() {
parts = url.split('/'), var banBtn = $(this);
active = parts[parts.length - 1]; var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
var parent = banBtn.parents('.users-box');
var uid = getUID(banBtn);
if (!isAdmin) {
if (isBanned) {
socket.emit('api:admin.user.unbanUser', uid);
banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('api:admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
}
});
}
}
jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
if (this.getAttribute('href').match(active)) {
jQuery(this.parentNode).addClass('active');
return false; return false;
} });
}); }
jQuery('#search-user').on('keyup', function() {
if (timeoutId !== 0) {
clearTimeout(timeoutId);
timeoutId = 0;
}
timeoutId = setTimeout(function() { jQuery('document').ready(function() {
var username = $('#search-user').val();
jQuery('.icon-spinner').removeClass('none'); var timeoutId = 0,
socket.emit('api:admin.user.search', username); loadingMoreUsers = false;
}, 250); var url = window.location.href,
}); parts = url.split('/'),
active = parts[parts.length - 1];
initUsers(); jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
socket.removeAllListeners('api:admin.user.search'); if (this.getAttribute('href').match(active)) {
jQuery(this.parentNode).addClass('active');
socket.on('api:admin.user.search', function(data) { return false;
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({ }
users: data });
}),
userListEl = document.querySelector('.users'); jQuery('#search-user').on('keyup', function() {
if (timeoutId !== 0) {
userListEl.innerHTML = html; clearTimeout(timeoutId);
jQuery('.icon-spinner').addClass('none'); timeoutId = 0;
}
if (data && data.length === 0) {
$('#user-notfound-notify').html('User not found!') timeoutId = setTimeout(function() {
.show() var username = $('#search-user').val();
.addClass('label-danger')
.removeClass('label-success'); jQuery('.icon-spinner').removeClass('none');
} else { socket.emit('api:admin.user.search', username);
$('#user-notfound-notify').html(data.length + ' user' + (data.length > 1 ? 's' : '') + ' found!')
.show() }, 250);
.addClass('label-success') });
.removeClass('label-danger');
}
initUsers(); initUsers();
});
function onUsersLoaded(users) { socket.removeAllListeners('api:admin.user.search');
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
users: users socket.on('api:admin.user.search', function(data) {
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
users: data
}),
userListEl = document.querySelector('.users');
userListEl.innerHTML = html;
jQuery('.icon-spinner').addClass('none');
if (data && data.length === 0) {
$('#user-notfound-notify').html('User not found!')
.show()
.addClass('label-danger')
.removeClass('label-success');
} else {
$('#user-notfound-notify').html(data.length + ' user' + (data.length > 1 ? 's' : '') + ' found!')
.show()
.addClass('label-success')
.removeClass('label-danger');
}
initUsers();
}); });
$('#users-container').append(html);
updateUserButtons();
}
function loadMoreUsers() { function onUsersLoaded(users) {
var set = ''; var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
if (active === 'latest') { users: users
set = 'users:joindate'; });
} else if (active === 'sort-posts') { $('#users-container').append(html);
set = 'users:postcount'; updateUserButtons();
} else if (active === 'sort-reputation') {
set = 'users:reputation';
} }
if (set) { function loadMoreUsers() {
loadingMoreUsers = true; var set = '';
socket.emit('api:users.loadMore', { if (active === 'latest') {
set: set, set = 'users:joindate';
after: $('#users-container').children().length } else if (active === 'sort-posts') {
}, function(data) { set = 'users:postcount';
if (data.users.length) { } else if (active === 'sort-reputation') {
onUsersLoaded(data.users); set = 'users:reputation';
} }
loadingMoreUsers = false;
}); if (set) {
loadingMoreUsers = true;
socket.emit('api:users.loadMore', {
set: set,
after: $('#users-container').children().length
}, function(data) {
if (data.users.length) {
onUsersLoaded(data.users);
}
loadingMoreUsers = false;
});
}
} }
}
$('#load-more-users-btn').on('click', loadMoreUsers); $('#load-more-users-btn').on('click', loadMoreUsers);
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreUsers) { if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
loadMoreUsers(); loadMoreUsers();
} }
}); });
}); });
};
}()); return Users;
});

@ -1,41 +1,86 @@
(function () { define(function () {
var cid = templates.get('category_id'), var Category = {};
room = 'category_' + cid,
twitterEl = document.getElementById('twitter-intent'), Category.init = function() {
facebookEl = document.getElementById('facebook-share'), var cid = templates.get('category_id'),
googleEl = document.getElementById('google-share'), room = 'category_' + cid,
twitter_url = templates.get('twitter-intent-url'), twitterEl = document.getElementById('twitter-intent'),
facebook_url = templates.get('facebook-share-url'), facebookEl = document.getElementById('facebook-share'),
google_url = templates.get('google-share-url'), googleEl = document.getElementById('google-share'),
loadingMoreTopics = false; twitter_url = templates.get('twitter-intent-url'),
facebook_url = templates.get('facebook-share-url'),
app.enter_room(room); google_url = templates.get('google-share-url'),
loadingMoreTopics = false;
twitterEl.addEventListener('click', function () {
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no'); app.enter_room(room);
return false;
}, false); twitterEl.addEventListener('click', function () {
facebookEl.addEventListener('click', function () { window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
window.open(facebook_url, '_blank', 'width=626,height=436,scrollbars=no,status=no'); return false;
return false; }, false);
}, false); facebookEl.addEventListener('click', function () {
googleEl.addEventListener('click', function () { window.open(facebook_url, '_blank', 'width=626,height=436,scrollbars=no,status=no');
window.open(google_url, '_blank', 'width=500,height=570,scrollbars=no,status=no'); return false;
return false; }, false);
}, false); googleEl.addEventListener('click', function () {
window.open(google_url, '_blank', 'width=500,height=570,scrollbars=no,status=no');
var new_post = document.getElementById('new_post'); return false;
new_post.onclick = function () { }, false);
require(['composer'], function (cmp) {
cmp.push(0, cid); var new_post = document.getElementById('new_post');
new_post.onclick = function () {
require(['composer'], function (cmp) {
cmp.push(0, cid);
});
}
ajaxify.register_events([
'event:new_topic'
]);
socket.on('event:new_topic', Category.onNewTopic);
socket.emit('api:categories.getRecentReplies', cid);
socket.on('api:categories.getRecentReplies', function (posts) {
if (!posts || posts.length === 0) {
return;
}
var recent_replies = document.getElementById('category_recent_replies');
recent_replies.innerHTML = '';
var frag = document.createDocumentFragment(),
li = document.createElement('li');
for (var i = 0, numPosts = posts.length; i < numPosts; i++) {
li.setAttribute('data-pid', posts[i].pid);
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded" src="' + posts[i].picture + '" class="" /></a>' +
'<a href="/topic/' + posts[i].topicSlug + '#' + posts[i].pid + '">' +
'<p>' +
posts[i].content +
'</p>' +
'<p class="meta"><strong>' + posts[i].username + '</strong></span> -<span class="timeago" title="' + posts[i].relativeTime + '"></span></p>' +
'</a>';
frag.appendChild(li.cloneNode(true));
recent_replies.appendChild(frag);
}
$('#category_recent_replies span.timeago').timeago();
}); });
}
ajaxify.register_events([ $(window).off('scroll').on('scroll', function (ev) {
'event:new_topic' var bottom = ($(document).height() - $(window).height()) * 0.9;
]);
function onNewTopic(data) { if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
Category.loadMoreTopics(cid);
}
});
};
Category.onNewTopic = function(data) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ var html = templates.prepare(templates['category'].blocks['topics']).parse({
topics: [data] topics: [data]
}), }),
@ -64,40 +109,7 @@
$('#topics-container span.timeago').timeago(); $('#topics-container span.timeago').timeago();
} }
socket.on('event:new_topic', onNewTopic); Category.onTopicsLoaded = function(topics) {
socket.emit('api:categories.getRecentReplies', cid);
socket.on('api:categories.getRecentReplies', function (posts) {
if (!posts || posts.length === 0) {
return;
}
var recent_replies = document.getElementById('category_recent_replies');
recent_replies.innerHTML = '';
var frag = document.createDocumentFragment(),
li = document.createElement('li');
for (var i = 0, numPosts = posts.length; i < numPosts; i++) {
li.setAttribute('data-pid', posts[i].pid);
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded" src="' + posts[i].picture + '" class="" /></a>' +
'<a href="/topic/' + posts[i].topicSlug + '#' + posts[i].pid + '">' +
'<p>' +
posts[i].content +
'</p>' +
'<p class="meta"><strong>' + posts[i].username + '</strong></span> -<span class="timeago" title="' + posts[i].relativeTime + '"></span></p>' +
'</a>';
frag.appendChild(li.cloneNode(true));
recent_replies.appendChild(frag);
}
$('#category_recent_replies span.timeago').timeago();
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ var html = templates.prepare(templates['category'].blocks['topics']).parse({
topics: topics topics: topics
@ -113,26 +125,18 @@
} }
function loadMoreTopics(cid) { Category.loadMoreTopics = function(cid) {
loadingMoreTopics = true; loadingMoreTopics = true;
socket.emit('api:category.loadMore', { socket.emit('api:category.loadMore', {
cid: cid, cid: cid,
after: $('#topics-container').children().length after: $('#topics-container').children().length
}, function (data) { }, function (data) {
if (data.topics.length) { if (data.topics.length) {
onTopicsLoaded(data.topics); Category.onTopicsLoaded(data.topics);
} }
loadingMoreTopics = false; loadingMoreTopics = false;
}); });
} }
$(window).off('scroll').on('scroll', function (ev) { return Category;
var bottom = ($(document).height() - $(window).height()) * 0.9; });
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics(cid);
}
});
})();

@ -1,7 +1,13 @@
(function() { define(['forum/accountheader'], function(header) {
$(document).ready(function() { var AccountHeader = {};
AccountHeader.init = function() {
header.init();
$('.user-favourite-posts .topic-row').on('click', function() { $('.user-favourite-posts .topic-row').on('click', function() {
ajaxify.go($(this).attr('topic-url')); ajaxify.go($(this).attr('topic-url'));
}); });
}); };
}());
return AccountHeader;
});

@ -1,18 +1,20 @@
(function() { define(['forum/accountheader'], function(header) {
var Followers = {};
var yourid = templates.get('yourid'), Followers.init = function() {
theirid = templates.get('theirid'), header.init();
followersCount = templates.get('followersCount');
$(document).ready(function() { var yourid = templates.get('yourid'),
theirid = templates.get('theirid'),
followersCount = templates.get('followersCount');
if (parseInt(followersCount, 10) === 0) {
$('#no-followers-notice').removeClass('hide');
}
app.addCommasToNumbers(); if (parseInt(followersCount, 10) === 0) {
$('#no-followers-notice').removeClass('hide');
}); }
app.addCommasToNumbers();
};
}()); return Followers;
});

@ -1,10 +1,12 @@
(function() { define(['forum/accountheader'], function(header) {
var Following = {};
var yourid = templates.get('yourid'), Following.init = function() {
theirid = templates.get('theirid'), header.init();
followingCount = templates.get('followingCount');
$(document).ready(function() { var yourid = templates.get('yourid'),
theirid = templates.get('theirid'),
followingCount = templates.get('followingCount');
if (parseInt(followingCount, 10) === 0) { if (parseInt(followingCount, 10) === 0) {
$('#no-following-notice').removeClass('hide'); $('#no-following-notice').removeClass('hide');
@ -34,7 +36,7 @@
} }
app.addCommasToNumbers(); app.addCommasToNumbers();
}); };
return Following;
}()); });

@ -1,60 +1,66 @@
(function() { define(function() {
// Alternate Logins var Login = {};
var altLoginEl = document.querySelector('.alt-logins');
altLoginEl.addEventListener('click', function(e) { Login.init = function() {
var target; // Alternate Logins
switch (e.target.nodeName) { var altLoginEl = document.querySelector('.alt-logins');
case 'LI': altLoginEl.addEventListener('click', function(e) {
target = e.target; var target;
break; switch (e.target.nodeName) {
case 'I': case 'LI':
target = e.target.parentNode; target = e.target;
break; break;
} case 'I':
if (target) { target = e.target.parentNode;
document.location.href = target.getAttribute('data-url'); break;
} }
}); if (target) {
document.location.href = target.getAttribute('data-url');
$('#login').on('click', function() { }
var loginData = { });
'username': $('#username').val(),
'password': $('#password').val(), $('#login').on('click', function() {
'_csrf': $('#csrf-token').val() var loginData = {
}; 'username': $('#username').val(),
'password': $('#password').val(),
$.ajax({ '_csrf': $('#csrf-token').val()
type: "POST", };
url: RELATIVE_PATH + '/login',
data: loginData, $.ajax({
success: function(data, textStatus, jqXHR) { type: "POST",
if (!data.success) { url: RELATIVE_PATH + '/login',
data: loginData,
success: function(data, textStatus, jqXHR) {
if (!data.success) {
$('#login-error-notify').show();
} else {
$('#login-error-notify').hide();
if(app.previousUrl.indexOf('/reset/') != -1)
window.location.replace(RELATIVE_PATH + "/?loggedin");
else
window.location.replace(app.previousUrl + "?loggedin");
app.loadConfig();
}
},
error: function(data, textStatus, jqXHR) {
$('#login-error-notify').show(); $('#login-error-notify').show();
} else { },
$('#login-error-notify').hide(); dataType: 'json',
if(app.previousUrl.indexOf('/reset/') != -1) async: true,
window.location.replace(RELATIVE_PATH + "/?loggedin"); timeout: 2000
else });
window.location.replace(app.previousUrl + "?loggedin");
return false;
app.loadConfig();
}
},
error: function(data, textStatus, jqXHR) {
$('#login-error-notify').show();
},
dataType: 'json',
async: true,
timeout: 2000
}); });
return false; $('#login-error-notify button').on('click', function() {
}); $('#login-error-notify').hide();
return false;
});
$('#login-error-notify button').on('click', function() { document.querySelector('#content input').focus();
$('#login-error-notify').hide(); };
return false;
});
document.querySelector('#content input').focus(); return Login;
}()); });

@ -1,40 +1,56 @@
(function() { define(function() {
var loadingMoreTopics = false; var Recent = {};
app.enter_room('recent_posts'); Recent.newTopicCount = 0;
Recent.newPostCount = 0;
Recent.loadingMoreTopics = false;
ajaxify.register_events([ Recent.init = function() {
'event:new_topic', app.enter_room('recent_posts');
'event:new_post'
]);
var newTopicCount = 0, ajaxify.register_events([
newPostCount = 0; 'event:new_topic',
'event:new_post'
]);
$('#new-topics-alert').on('click', function() { $('#new-topics-alert').on('click', function() {
$(this).hide(); $(this).hide();
}); });
socket.on('event:new_topic', function(data) {
++Recent.newTopicCount;
Recent.updateAlertText();
socket.on('event:new_topic', function(data) { });
socket.on('event:new_post', function(data) {
++Recent.newPostCount;
Recent.updateAlertText();
});
++newTopicCount; $(window).off('scroll').on('scroll', function() {
updateAlertText(); var bottom = ($(document).height() - $(window).height()) * 0.9;
}); if ($(window).scrollTop() > bottom && !Recent.loadingMoreTopics) {
Recent.loadMoreTopics();
}
});
};
function updateAlertText() { Recent.updateAlertText = function() {
var text = ''; var text = '';
if (newTopicCount > 1) if (Recent.newTopicCount > 1)
text = 'There are ' + newTopicCount + ' new topics'; text = 'There are ' + Recent.newTopicCount + ' new topics';
else if (newTopicCount === 1) else if (Recent.newTopicCount === 1)
text = 'There is 1 new topic'; text = 'There is 1 new topic';
else else
text = 'There are no new topics'; text = 'There are no new topics';
if (newPostCount > 1) if (Recent.newPostCount > 1)
text += ' and ' + newPostCount + ' new posts.'; text += ' and ' + Recent.newPostCount + ' new posts.';
else if (newPostCount === 1) else if (Recent.newPostCount === 1)
text += ' and 1 new post.'; text += ' and 1 new post.';
else else
text += ' and no new posts.'; text += ' and no new posts.';
@ -44,12 +60,7 @@
$('#new-topics-alert').html(text).fadeIn('slow'); $('#new-topics-alert').html(text).fadeIn('slow');
} }
socket.on('event:new_post', function(data) { Recent.onTopicsLoaded = function(topics) {
++newPostCount;
updateAlertText();
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['recent'].blocks['topics']).parse({ var html = templates.prepare(templates['recent'].blocks['topics']).parse({
topics: topics topics: topics
@ -61,25 +72,17 @@
container.append(html); container.append(html);
} }
function loadMoreTopics() { Recent.loadMoreTopics = function() {
loadingMoreTopics = true; Recent.loadingMoreTopics = true;
socket.emit('api:topics.loadMoreRecentTopics', { socket.emit('api:topics.loadMoreRecentTopics', {
after: $('#topics-container').children().length after: $('#topics-container').children().length
}, function(data) { }, function(data) {
if (data.topics && data.topics.length) { if (data.topics && data.topics.length) {
onTopicsLoaded(data.topics); Recent.onTopicsLoaded(data.topics);
} }
loadingMoreTopics = false; Recent.loadingMoreTopics = false;
}); });
} }
$(window).off('scroll').on('scroll', function() { return Recent;
var bottom = ($(document).height() - $(window).height()) * 0.9; });
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics();
}
});
})();

@ -1,154 +1,159 @@
(function() { define(function() {
var username = $('#username'), var Register = {};
password = $('#password'),
password_confirm = $('#password-confirm'), Register.init = function() {
register = $('#register'), var username = $('#username'),
emailEl = $('#email'), password = $('#password'),
username_notify = $('#username-notify'), password_confirm = $('#password-confirm'),
email_notify = $('#email-notify'), register = $('#register'),
password_notify = $('#password-notify'), emailEl = $('#email'),
password_confirm_notify = $('#password-confirm-notify'), username_notify = $('#username-notify'),
validationError = false, email_notify = $('#email-notify'),
successIcon = '<i class="icon icon-ok"></i>'; password_notify = $('#password-notify'),
password_confirm_notify = $('#password-confirm-notify'),
$('#referrer').val(app.previousUrl); validationError = false,
successIcon = '<i class="icon icon-ok"></i>';
function showError(element, msg) {
element.html(msg); $('#referrer').val(app.previousUrl);
element.parent()
.removeClass('alert-success') function showError(element, msg) {
.addClass('alert-danger'); element.html(msg);
element.show(); element.parent()
validationError = true; .removeClass('alert-success')
} .addClass('alert-danger');
element.show();
function showSuccess(element, msg) {
element.html(msg);
element.parent()
.removeClass('alert-danger')
.addClass('alert-success');
element.show();
}
function validateEmail() {
if (!emailEl.val()) {
validationError = true; validationError = true;
return;
} }
if (!utils.isEmailValid(emailEl.val())) { function showSuccess(element, msg) {
showError(email_notify, 'Invalid email address.'); element.html(msg);
} else element.parent()
socket.emit('user.email.exists', { .removeClass('alert-danger')
email: emailEl.val() .addClass('alert-success');
}); element.show();
}
emailEl.on('blur', function() {
validateEmail();
});
function validateUsername() {
if (!username.val()) {
validationError = true;
return;
}
if (username.val().length < config.minimumUsernameLength) {
showError(username_notify, 'Username too short!');
} else if (username.val().length > config.maximumUsernameLength) {
showError(username_notify, 'Username too long!');
} else if (!utils.isUserNameValid(username.val())) {
showError(username_notify, 'Invalid username!');
} else {
socket.emit('user.exists', {
username: username.val()
});
}
}
username.on('keyup', function() {
jQuery('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
});
username.on('blur', function() {
validateUsername();
});
function validatePassword() {
if (!password.val()) {
validationError = true;
return;
}
if (password.val().length < config.minimumPasswordLength) {
showError(password_notify, 'Password too short!');
} else if (!utils.isPasswordValid(password.val())) {
showError(password_notify, 'Invalid password!');
} else {
showSuccess(password_notify, successIcon);
} }
if (password.val() !== password_confirm.val() && password_confirm.val() !== '') { function validateEmail() {
showError(password_confirm_notify, 'Passwords must match!'); if (!emailEl.val()) {
validationError = true;
return;
}
if (!utils.isEmailValid(emailEl.val())) {
showError(email_notify, 'Invalid email address.');
} else
socket.emit('user.email.exists', {
email: emailEl.val()
});
} }
}
$(password).on('blur', function() {
validatePassword();
});
function validatePasswordConfirm() { emailEl.on('blur', function() {
if (!password.val() || password_notify.hasClass('alert-error')) { validateEmail();
return; });
function validateUsername() {
if (!username.val()) {
validationError = true;
return;
}
if (username.val().length < config.minimumUsernameLength) {
showError(username_notify, 'Username too short!');
} else if (username.val().length > config.maximumUsernameLength) {
showError(username_notify, 'Username too long!');
} else if (!utils.isUserNameValid(username.val())) {
showError(username_notify, 'Invalid username!');
} else {
socket.emit('user.exists', {
username: username.val()
});
}
} }
if (password.val() !== password_confirm.val()) { username.on('keyup', function() {
showError(password_confirm_notify, 'Passwords must match!'); jQuery('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
} else { });
showSuccess(password_confirm_notify, successIcon); username.on('blur', function() {
validateUsername();
});
function validatePassword() {
if (!password.val()) {
validationError = true;
return;
}
if (password.val().length < config.minimumPasswordLength) {
showError(password_notify, 'Password too short!');
} else if (!utils.isPasswordValid(password.val())) {
showError(password_notify, 'Invalid password!');
} else {
showSuccess(password_notify, successIcon);
}
if (password.val() !== password_confirm.val() && password_confirm.val() !== '') {
showError(password_confirm_notify, 'Passwords must match!');
}
} }
}
$(password_confirm).on('blur', function() { $(password).on('blur', function() {
validatePasswordConfirm(); validatePassword();
}); });
ajaxify.register_events(['user.exists', 'user.email.exists']); function validatePasswordConfirm() {
if (!password.val() || password_notify.hasClass('alert-error')) {
return;
}
socket.on('user.exists', function(data) { if (password.val() !== password_confirm.val()) {
if (data.exists === true) { showError(password_confirm_notify, 'Passwords must match!');
showError(username_notify, 'Username already taken!'); } else {
} else { showSuccess(password_confirm_notify, successIcon);
showSuccess(username_notify, successIcon); }
} }
});
socket.on('user.email.exists', function(data) { $(password_confirm).on('blur', function() {
if (data.exists === true) { validatePasswordConfirm();
showError(email_notify, 'Email address already taken!'); });
} else {
showSuccess(email_notify, successIcon); ajaxify.register_events(['user.exists', 'user.email.exists']);
socket.on('user.exists', function(data) {
if (data.exists === true) {
showError(username_notify, 'Username already taken!');
} else {
showSuccess(username_notify, successIcon);
}
});
socket.on('user.email.exists', function(data) {
if (data.exists === true) {
showError(email_notify, 'Email address already taken!');
} else {
showSuccess(email_notify, successIcon);
}
});
// Alternate Logins
$('.alt-logins li').on('click', function(e) {
document.location.href = $(this).attr('data-url');
});
function validateForm() {
validationError = false;
validateEmail();
validateUsername();
validatePassword();
validatePasswordConfirm();
return validationError;
} }
});
// Alternate Logins
$('.alt-logins li').on('click', function(e) {
document.location.href = $(this).attr('data-url');
});
function validateForm() {
validationError = false;
validateEmail();
validateUsername();
validatePassword();
validatePasswordConfirm();
return validationError;
}
register.on('click', function(e) { register.on('click', function(e) {
if (validateForm()) e.preventDefault(); if (validateForm()) e.preventDefault();
}); });
};
}()); return Register;
});

@ -1,41 +1,47 @@
(function() { define(function() {
var inputEl = document.getElementById('email'), var ResetPassword = {};
errorEl = document.getElementById('error'),
errorTextEl = errorEl.querySelector('p');
document.getElementById('reset').onclick = function() { ResetPassword.init = function() {
if (inputEl.value.length > 0 && inputEl.value.indexOf('@') !== -1) { var inputEl = document.getElementById('email'),
socket.emit('user:reset.send', { errorEl = document.getElementById('error'),
email: inputEl.value errorTextEl = errorEl.querySelector('p');
});
} else { document.getElementById('reset').onclick = function() {
jQuery('#success').hide(); if (inputEl.value.length > 0 && inputEl.value.indexOf('@') !== -1) {
jQuery(errorEl).show(); socket.emit('user:reset.send', {
errorTextEl.innerHTML = 'Please enter a valid email'; email: inputEl.value
} });
}; } else {
jQuery('#success').hide();
jQuery(errorEl).show();
errorTextEl.innerHTML = 'Please enter a valid email';
}
};
ajaxify.register_events(['user.send_reset']); ajaxify.register_events(['user.send_reset']);
socket.on('user.send_reset', function(data) { socket.on('user.send_reset', function(data) {
var submitEl = document.getElementById('reset'); var submitEl = document.getElementById('reset');
if (data.status === 'ok') { if (data.status === 'ok') {
jQuery('#error').hide(); jQuery('#error').hide();
jQuery('#success').show(); jQuery('#success').show();
jQuery('#success p').html('An email has been dispatched to "' + data.email + '" with instructions on setting a new password.'); jQuery('#success p').html('An email has been dispatched to "' + data.email + '" with instructions on setting a new password.');
inputEl.value = ''; inputEl.value = '';
} else { } else {
jQuery('#success').hide(); jQuery('#success').hide();
jQuery(errorEl).show(); jQuery(errorEl).show();
switch (data.message) { switch (data.message) {
case 'invalid-email': case 'invalid-email':
errorTextEl.innerHTML = 'The email you put in (<span>' + data.email + '</span>) is not registered with us. Please try again.'; errorTextEl.innerHTML = 'The email you put in (<span>' + data.email + '</span>) is not registered with us. Please try again.';
break; break;
case 'send-failed': case 'send-failed':
errorTextEl.innerHTML = 'There was a problem sending the reset code. Please try again later.'; errorTextEl.innerHTML = 'There was a problem sending the reset code. Please try again later.';
break; break;
}
} }
} });
}); };
}());
return ResetPassword;
});

@ -1,52 +1,58 @@
(function() { define(function() {
var reset_code = templates.get('reset_code'); var ResetCode = {};
var resetEl = document.getElementById('reset'), ResetCode.init = function() {
password = document.getElementById('password'), var reset_code = templates.get('reset_code');
repeat = document.getElementById('repeat'),
noticeEl = document.getElementById('notice'); var resetEl = document.getElementById('reset'),
password = document.getElementById('password'),
resetEl.addEventListener('click', function() { repeat = document.getElementById('repeat'),
if (password.value.length < 6) { noticeEl = document.getElementById('notice');
$('#error').hide();
noticeEl.querySelector('strong').innerHTML = 'Invalid Password'; resetEl.addEventListener('click', function() {
noticeEl.querySelector('p').innerHTML = 'The password entered is too short, please pick a different password.'; if (password.value.length < 6) {
noticeEl.style.display = 'block'; $('#error').hide();
} else if (password.value !== repeat.value) { noticeEl.querySelector('strong').innerHTML = 'Invalid Password';
$('#error').hide(); noticeEl.querySelector('p').innerHTML = 'The password entered is too short, please pick a different password.';
noticeEl.querySelector('strong').innerHTML = 'Invalid Password'; noticeEl.style.display = 'block';
noticeEl.querySelector('p').innerHTML = 'The two passwords you\'ve entered do not match.'; } else if (password.value !== repeat.value) {
noticeEl.style.display = 'block'; $('#error').hide();
} else { noticeEl.querySelector('strong').innerHTML = 'Invalid Password';
socket.emit('user:reset.commit', { noticeEl.querySelector('p').innerHTML = 'The two passwords you\'ve entered do not match.';
code: reset_code, noticeEl.style.display = 'block';
password: password.value } else {
}); socket.emit('user:reset.commit', {
} code: reset_code,
}, false); password: password.value
});
// Enable the form if the code is valid }
socket.emit('user:reset.valid', { }, false);
code: reset_code
}); // Enable the form if the code is valid
socket.emit('user:reset.valid', {
code: reset_code
ajaxify.register_events(['user:reset.valid', 'user:reset.commit']); });
socket.on('user:reset.valid', function(data) {
if ( !! data.valid) resetEl.disabled = false;
else { ajaxify.register_events(['user:reset.valid', 'user:reset.commit']);
var formEl = document.getElementById('reset-form'); socket.on('user:reset.valid', function(data) {
// Show error message if ( !! data.valid) resetEl.disabled = false;
$('#error').show(); else {
formEl.parentNode.removeChild(formEl); var formEl = document.getElementById('reset-form');
} // Show error message
}) $('#error').show();
formEl.parentNode.removeChild(formEl);
socket.on('user:reset.commit', function(data) { }
if (data.status === 'ok') { })
$('#error').hide();
$('#notice').hide(); socket.on('user:reset.commit', function(data) {
$('#success').show(); if (data.status === 'ok') {
} $('#error').hide();
}); $('#notice').hide();
}()); $('#success').show();
}
});
};
return ResetCode;
});

@ -1,6 +1,7 @@
(function() { define(function() {
var Search = {};
$(document).ready(function() { Search.init = function() {
var searchQuery = $('#topics-container').attr('data-search-query'); var searchQuery = $('#topics-container').attr('data-search-query');
$('.search-result-text').each(function() { $('.search-result-text').each(function() {
@ -21,4 +22,5 @@
}); });
}); });
})(); return Search;
});

File diff suppressed because it is too large Load Diff

@ -1,114 +1,119 @@
(function() { define(function() {
var loadingMoreTopics = false; var Unread = {};
app.enter_room('recent_posts'); Unread.init = function() {
var loadingMoreTopics = false;
ajaxify.register_events([
'event:new_topic', app.enter_room('recent_posts');
'event:new_post'
]); ajaxify.register_events([
'event:new_topic',
var newTopicCount = 0, 'event:new_post'
newPostCount = 0; ]);
$('#new-topics-alert').on('click', function() { var newTopicCount = 0,
$(this).hide(); newPostCount = 0;
});
$('#new-topics-alert').on('click', function() {
socket.on('event:new_topic', function(data) { $(this).hide();
++newTopicCount;
updateAlertText();
});
function updateAlertText() {
var text = '';
if (newTopicCount > 1)
text = 'There are ' + newTopicCount + ' new topics';
else if (newTopicCount === 1)
text = 'There is 1 new topic';
else
text = 'There are no new topics';
if (newPostCount > 1)
text += ' and ' + newPostCount + ' new posts.';
else if (newPostCount === 1)
text += ' and 1 new post.';
else
text += ' and no new posts.';
text += ' Click here to reload.';
$('#new-topics-alert').html(text).fadeIn('slow');
}
socket.on('event:new_post', function(data) {
++newPostCount;
updateAlertText();
});
$('#mark-allread-btn').on('click', function() {
var btn = $(this);
socket.emit('api:topics.markAllRead', {}, function(success) {
if (success) {
btn.remove();
$('#topics-container').empty();
$('#category-no-topics').removeClass('hidden');
app.alertSuccess('All topics marked as read!');
$('#numUnreadBadge')
.removeClass('badge-important')
.addClass('badge-inverse')
.html('0');
} else {
app.alertError('There was an error marking topics read!');
}
}); });
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['unread'].blocks['topics']).parse({
topics: topics
}),
container = $('#topics-container');
$('#category-no-topics').remove();
container.append(html);
}
function loadMoreTopics() {
loadingMoreTopics = true;
socket.emit('api:topics.loadMoreUnreadTopics', {
after: parseInt($('#topics-container').attr('data-next-start'), 10)
}, function(data) {
if (data.topics && data.topics.length) {
onTopicsLoaded(data.topics);
$('#topics-container').attr('data-next-start', data.nextStart);
} else {
$('#load-more-btn').hide();
}
loadingMoreTopics = false; socket.on('event:new_topic', function(data) {
++newTopicCount;
updateAlertText();
}); });
}
$(window).off('scroll').on('scroll', function() { function updateAlertText() {
var bottom = ($(document).height() - $(window).height()) * 0.9; var text = '';
if ($(window).scrollTop() > bottom && !loadingMoreTopics) { if (newTopicCount > 1)
loadMoreTopics(); text = 'There are ' + newTopicCount + ' new topics';
else if (newTopicCount === 1)
text = 'There is 1 new topic';
else
text = 'There are no new topics';
if (newPostCount > 1)
text += ' and ' + newPostCount + ' new posts.';
else if (newPostCount === 1)
text += ' and 1 new post.';
else
text += ' and no new posts.';
text += ' Click here to reload.';
$('#new-topics-alert').html(text).fadeIn('slow');
}
socket.on('event:new_post', function(data) {
++newPostCount;
updateAlertText();
});
$('#mark-allread-btn').on('click', function() {
var btn = $(this);
socket.emit('api:topics.markAllRead', {}, function(success) {
if (success) {
btn.remove();
$('#topics-container').empty();
$('#category-no-topics').removeClass('hidden');
app.alertSuccess('All topics marked as read!');
$('#numUnreadBadge')
.removeClass('badge-important')
.addClass('badge-inverse')
.html('0');
} else {
app.alertError('There was an error marking topics read!');
}
});
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['unread'].blocks['topics']).parse({
topics: topics
}),
container = $('#topics-container');
$('#category-no-topics').remove();
container.append(html);
} }
});
function loadMoreTopics() {
loadingMoreTopics = true;
socket.emit('api:topics.loadMoreUnreadTopics', {
after: parseInt($('#topics-container').attr('data-next-start'), 10)
}, function(data) {
if (data.topics && data.topics.length) {
onTopicsLoaded(data.topics);
$('#topics-container').attr('data-next-start', data.nextStart);
} else {
$('#load-more-btn').hide();
}
loadingMoreTopics = false;
});
}
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20) $(window).off('scroll').on('scroll', function() {
$('#load-more-btn').show(); var bottom = ($(document).height() - $(window).height()) * 0.9;
$('#load-more-btn').on('click', function() { if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics(); loadMoreTopics();
}); }
});
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20)
$('#load-more-btn').show();
$('#load-more-btn').on('click', function() {
loadMoreTopics();
});
};
})(); return Unread;
});

@ -1,6 +1,7 @@
(function() { define(function() {
var Users = {};
$(document).ready(function() { Users.init = function() {
var timeoutId = 0; var timeoutId = 0;
var loadingMoreUsers = false; var loadingMoreUsers = false;
@ -131,6 +132,7 @@
loadMoreUsers(); loadMoreUsers();
} }
}); });
}); };
}()); return Users;
});

@ -98,6 +98,3 @@
<input type="hidden" template-variable="yourid" value="{yourid}" /> <input type="hidden" template-variable="yourid" value="{yourid}" />
<input type="hidden" template-variable="theirid" value="{theirid}" /> <input type="hidden" template-variable="theirid" value="{theirid}" />
<input type="hidden" template-type="boolean" template-variable="isFollowing" value="{isFollowing}" /> <input type="hidden" template-type="boolean" template-variable="isFollowing" value="{isFollowing}" />
<script type="text/javascript" src="{relative_path}/src/forum/account.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>

@ -174,9 +174,3 @@
</div> </div>
</div> </div>
<input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" />
<input type="hidden" template-variable="uploadedpicture" value="{uploadedpicture}" />
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountedit.js"></script>

@ -26,6 +26,3 @@
<a id="submitBtn" href="#" class="btn btn-primary">Save changes</a> <a id="submitBtn" href="#" class="btn btn-primary">Save changes</a>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountsettings.js"></script>

@ -101,5 +101,3 @@
<div class="col-md-3"><i class="icon-adn"></i></div><div class="col-md-3"><i class="icon-android"></i></div><div class="col-md-3"><i class="icon-apple"></i></div><div class="col-md-3"><i class="icon-bitbucket"></i></div><div class="col-md-3"><i class="icon-bitbucket-sign"></i></div><div class="col-md-3"><i class="icon-bitcoin"></i></div><div class="col-md-3"><i class="icon-btc"></i></div><div class="col-md-3"><i class="icon-css3"></i></div><div class="col-md-3"><i class="icon-dribbble"></i></div><div class="col-md-3"><i class="icon-dropbox"></i></div><div class="col-md-3"><i class="icon-facebook"></i></div><div class="col-md-3"><i class="icon-facebook-sign"></i></div><div class="col-md-3"><i class="icon-flickr"></i></div><div class="col-md-3"><i class="icon-foursquare"></i></div><div class="col-md-3"><i class="icon-github"></i></div><div class="col-md-3"><i class="icon-github-alt"></i></div><div class="col-md-3"><i class="icon-github-sign"></i></div><div class="col-md-3"><i class="icon-gittip"></i></div><div class="col-md-3"><i class="icon-google-plus"></i></div><div class="col-md-3"><i class="icon-google-plus-sign"></i></div><div class="col-md-3"><i class="icon-html5"></i></div><div class="col-md-3"><i class="icon-instagram"></i></div><div class="col-md-3"><i class="icon-linkedin"></i></div><div class="col-md-3"><i class="icon-linkedin-sign"></i></div><div class="col-md-3"><i class="icon-linux"></i></div><div class="col-md-3"><i class="icon-maxcdn"></i></div><div class="col-md-3"><i class="icon-pinterest"></i></div><div class="col-md-3"><i class="icon-pinterest-sign"></i></div><div class="col-md-3"><i class="icon-renren"></i></div><div class="col-md-3"><i class="icon-skype"></i></div><div class="col-md-3"><i class="icon-stackexchange"></i></div><div class="col-md-3"><i class="icon-trello"></i></div><div class="col-md-3"><i class="icon-tumblr"></i></div><div class="col-md-3"><i class="icon-tumblr-sign"></i></div><div class="col-md-3"><i class="icon-twitter"></i></div><div class="col-md-3"><i class="icon-twitter-sign"></i></div><div class="col-md-3"><i class="icon-vk"></i></div><div class="col-md-3"><i class="icon-weibo"></i></div><div class="col-md-3"><i class="icon-windows"></i></div><div class="col-md-3"><i class="icon-xing"></i></div><div class="col-md-3"><i class="icon-xing-sign"></i></div><div class="col-md-3"><i class="icon-youtube"></i></div><div class="col-md-3"><i class="icon-youtube-play"></i></div><div class="col-md-3"><i class="icon-youtube-sign"></i></div> <div class="col-md-3"><i class="icon-adn"></i></div><div class="col-md-3"><i class="icon-android"></i></div><div class="col-md-3"><i class="icon-apple"></i></div><div class="col-md-3"><i class="icon-bitbucket"></i></div><div class="col-md-3"><i class="icon-bitbucket-sign"></i></div><div class="col-md-3"><i class="icon-bitcoin"></i></div><div class="col-md-3"><i class="icon-btc"></i></div><div class="col-md-3"><i class="icon-css3"></i></div><div class="col-md-3"><i class="icon-dribbble"></i></div><div class="col-md-3"><i class="icon-dropbox"></i></div><div class="col-md-3"><i class="icon-facebook"></i></div><div class="col-md-3"><i class="icon-facebook-sign"></i></div><div class="col-md-3"><i class="icon-flickr"></i></div><div class="col-md-3"><i class="icon-foursquare"></i></div><div class="col-md-3"><i class="icon-github"></i></div><div class="col-md-3"><i class="icon-github-alt"></i></div><div class="col-md-3"><i class="icon-github-sign"></i></div><div class="col-md-3"><i class="icon-gittip"></i></div><div class="col-md-3"><i class="icon-google-plus"></i></div><div class="col-md-3"><i class="icon-google-plus-sign"></i></div><div class="col-md-3"><i class="icon-html5"></i></div><div class="col-md-3"><i class="icon-instagram"></i></div><div class="col-md-3"><i class="icon-linkedin"></i></div><div class="col-md-3"><i class="icon-linkedin-sign"></i></div><div class="col-md-3"><i class="icon-linux"></i></div><div class="col-md-3"><i class="icon-maxcdn"></i></div><div class="col-md-3"><i class="icon-pinterest"></i></div><div class="col-md-3"><i class="icon-pinterest-sign"></i></div><div class="col-md-3"><i class="icon-renren"></i></div><div class="col-md-3"><i class="icon-skype"></i></div><div class="col-md-3"><i class="icon-stackexchange"></i></div><div class="col-md-3"><i class="icon-trello"></i></div><div class="col-md-3"><i class="icon-tumblr"></i></div><div class="col-md-3"><i class="icon-tumblr-sign"></i></div><div class="col-md-3"><i class="icon-twitter"></i></div><div class="col-md-3"><i class="icon-twitter-sign"></i></div><div class="col-md-3"><i class="icon-vk"></i></div><div class="col-md-3"><i class="icon-weibo"></i></div><div class="col-md-3"><i class="icon-windows"></i></div><div class="col-md-3"><i class="icon-xing"></i></div><div class="col-md-3"><i class="icon-xing-sign"></i></div><div class="col-md-3"><i class="icon-youtube"></i></div><div class="col-md-3"><i class="icon-youtube-play"></i></div><div class="col-md-3"><i class="icon-youtube-sign"></i></div>
<div class="col-md-3"><i class="icon-ambulance"></i></div><div class="col-md-3"><i class="icon-h-sign"></i></div><div class="col-md-3"><i class="icon-hospital"></i></div><div class="col-md-3"><i class="icon-medkit"></i></div><div class="col-md-3"><i class="icon-plus-sign-alt"></i></div><div class="col-md-3"><i class="icon-stethoscope"></i></div><div class="col-md-3"><i class="icon-user-md"></i></div> <div class="col-md-3"><i class="icon-ambulance"></i></div><div class="col-md-3"><i class="icon-h-sign"></i></div><div class="col-md-3"><i class="icon-hospital"></i></div><div class="col-md-3"><i class="icon-medkit"></i></div><div class="col-md-3"><i class="icon-plus-sign-alt"></i></div><div class="col-md-3"><i class="icon-stethoscope"></i></div><div class="col-md-3"><i class="icon-user-md"></i></div>
</div></div></div> </div></div></div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/categories.js"></script>

@ -17,10 +17,7 @@
<button class="btn btn-lg btn-primary" id="save">Save</button> <button class="btn btn-lg btn-primary" id="save">Save</button>
<script> <script>
var loadDelay = setInterval(function() { require(['forum/admin/settings'], function(Settings) {
if (nodebb_admin) { Settings.prepare();
nodebb_admin.prepare(); });
clearInterval(loadDelay);
}
}, 500);
</script> </script>

@ -9,7 +9,7 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
$.getScript(RELATIVE_PATH + '/src/forum/admin/footer.js'); require(['forum/admin/footer']);
</script> </script>
</body> </body>

@ -17,10 +17,7 @@
<button class="btn btn-lg btn-primary" id="save">Save</button> <button class="btn btn-lg btn-primary" id="save">Save</button>
<script> <script>
var loadDelay = setInterval(function() { require(['forum/admin/settings'], function(Settings) {
if (nodebb_admin) { Settings.prepare();
nodebb_admin.prepare(); });
clearInterval(loadDelay);
}
}, 500);
</script> </script>

@ -96,5 +96,3 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/groups.js"></script>

@ -7,7 +7,6 @@
var RELATIVE_PATH = "{relative_path}"; var RELATIVE_PATH = "{relative_path}";
</script> </script>
<link id="base-theme" href="{relative_path}/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen"> <link id="base-theme" href="{relative_path}/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="{relative_path}/vendor/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css"> <link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script> <script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript" src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script> <script type="text/javascript" src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
@ -25,7 +24,10 @@
<script> <script>
require.config({ require.config({
baseUrl: "{relative_path}/src/modules", baseUrl: "{relative_path}/src/modules",
waitSeconds: 3 waitSeconds: 3,
paths: {
"forum": '../forum'
}
}); });
</script> </script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css"> <link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">

@ -18,5 +18,3 @@
</p> </p>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/index.js"></script>

@ -24,10 +24,7 @@
<button class="btn btn-lg btn-primary" id="save" checked>Save</button> <button class="btn btn-lg btn-primary" id="save" checked>Save</button>
<script> <script>
var loadDelay = setInterval(function() { require(['forum/admin/settings'], function(Settings) {
if (nodebb_admin) { Settings.prepare();
nodebb_admin.prepare(); });
clearInterval(loadDelay);
}
}, 500);
</script> </script>

@ -21,5 +21,3 @@
Full documentation regarding plugin authoring can be found in the <a target="_blank" href="https://github.com/designcreateplay/NodeBB/wiki/Writing-Plugins-for-NodeBB">NodeBB Wiki</a>. Full documentation regarding plugin authoring can be found in the <a target="_blank" href="https://github.com/designcreateplay/NodeBB/wiki/Writing-Plugins-for-NodeBB">NodeBB Wiki</a>.
</p> </p>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/plugins.js"></script>

@ -8,6 +8,8 @@
<input class="form-control" type="text" placeholder="Your Community Name" data-field="title" /><br /> <input class="form-control" type="text" placeholder="Your Community Name" data-field="title" /><br />
<label>Site Description</label> <label>Site Description</label>
<input type="text" class="form-control" placeholder="A short description about your community" data-field="description" /><br /> <input type="text" class="form-control" placeholder="A short description about your community" data-field="description" /><br />
<label>Site Keywords</label>
<input type="text" class="form-control" placeholder="Keywords describing your community, comma-seperated" data-field="keywords" /><br />
<label>Imgur Client ID</label> <label>Imgur Client ID</label>
<input type="text" class="form-control" placeholder="Imgur ClientID for image uploads" data-field="imgurClientID" /><br /> <input type="text" class="form-control" placeholder="Imgur ClientID for image uploads" data-field="imgurClientID" /><br />
<label>Maximum User Image Size</label> <label>Maximum User Image Size</label>
@ -70,17 +72,18 @@
<strong>Post Delay</strong><br /> <input type="text" class="form-control" value="10000" data-field="postDelay"><br /> <strong>Post Delay</strong><br /> <input type="text" class="form-control" value="10000" data-field="postDelay"><br />
<strong>Minimum Title Length</strong><br /> <input type="text" class="form-control" value="3" data-field="minimumTitleLength"><br /> <strong>Minimum Title Length</strong><br /> <input type="text" class="form-control" value="3" data-field="minimumTitleLength"><br />
<strong>Minimum Post Length</strong><br /> <input type="text" class="form-control" value="8" data-field="minimumPostLength"><br /> <strong>Minimum Post Length</strong><br /> <input type="text" class="form-control" value="8" data-field="minimumPostLength"><br />
<div class="checkbox">
<label>
<input type="checkbox" data-field="useOutgoingLinksPage"> <strong>Use Outgoing Links Warning Page</strong>
</label>
</div>
</div> </div>
</form> </form>
<button class="btn btn-lg btn-primary" id="save">Save</button> <button class="btn btn-lg btn-primary" id="save">Save</button>
<script> <script>
var loadDelay = setInterval(function() { require(['forum/admin/settings'], function(Settings) {
if (nodebb_admin) { Settings.prepare();
nodebb_admin.prepare(); });
clearInterval(loadDelay);
}
}, 500);
</script> </script>

@ -20,7 +20,7 @@ jQuery(document).ready(function () {
slug = 'category/' + category.slug; slug = 'category/' + category.slug;
asyncTest( "Loading Category '" + category.name + "' located at " + slug, function() { asyncTest( "Loading Category '" + category.name + "' located at " + slug, function() {
jQuery.get(config.api_url + slug, function(data) { jQuery.get(RELATIVE_PATH + '/api/' + slug, function(data) {
ok( data.category_name, JSON.stringify(data) ); //todo: check this against data.categories ok( data.category_name, JSON.stringify(data) ); //todo: check this against data.categories
start(); start();
}); });

@ -23,4 +23,10 @@
<button class="btn btn-warning" id="revert_theme">Revert</button> This will remove any custom theme applied to your NodeBB, and restore the base theme. <button class="btn btn-warning" id="revert_theme">Revert</button> This will remove any custom theme applied to your NodeBB, and restore the base theme.
</p> </p>
<script type="text/javascript" src="{relative_path}/src/forum/admin/themes.js"></script> <script>
var bootswatchListener = function(data) {
require(['forum/admin/themes'], function(t) {
t.render(data);
});
}
</script>

@ -22,5 +22,3 @@
<div class="text-center"> <div class="text-center">
<button id="topics_loadmore" class="btn btn-primary btn-lg">Load More Topics</button> <button id="topics_loadmore" class="btn btn-primary btn-lg">Load More Topics</button>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/topics.js"></script>

@ -17,10 +17,7 @@
<button class="btn btn-lg btn-primary" id="save">Save</button> <button class="btn btn-lg btn-primary" id="save">Save</button>
<script> <script>
var loadDelay = setInterval(function() { require(['forum/admin/settings'], function(Settings) {
if (nodebb_admin) { Settings.prepare();
nodebb_admin.prepare(); });
clearInterval(loadDelay);
}
}, 500);
</script> </script>

@ -42,6 +42,3 @@
<button id="load-more-users-btn" class="btn btn-primary">Load More</button> <button id="load-more-users-btn" class="btn btn-primary">Load More</button>
</div> </div>
<input type="hidden" template-variable="yourid" value="{yourid}" /> <input type="hidden" template-variable="yourid" value="{yourid}" />
<script type="text/javascript" src="{relative_path}/src/forum/admin/users.js"></script>

@ -97,5 +97,3 @@
<input type="hidden" template-variable="twitter-intent-url" value="{twitter-intent-url}" /> <input type="hidden" template-variable="twitter-intent-url" value="{twitter-intent-url}" />
<input type="hidden" template-variable="facebook-share-url" value="{facebook-share-url}" /> <input type="hidden" template-variable="facebook-share-url" value="{facebook-share-url}" />
<input type="hidden" template-variable="google-share-url" value="{google-share-url}" /> <input type="hidden" template-variable="google-share-url" value="{google-share-url}" />
<script type="text/javascript" src="{relative_path}/src/forum/category.js"></script>

@ -22,6 +22,3 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/favourites.js"></script>

@ -36,6 +36,3 @@
<input type="hidden" template-variable="yourid" value="{yourid}" /> <input type="hidden" template-variable="yourid" value="{yourid}" />
<input type="hidden" template-variable="theirid" value="{theirid}" /> <input type="hidden" template-variable="theirid" value="{theirid}" />
<input type="hidden" template-variable="followersCount" value="{followersCount}" /> <input type="hidden" template-variable="followersCount" value="{followersCount}" />
<script type="text/javascript" src="{relative_path}/src/forum/followers.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>

@ -38,6 +38,3 @@
<input type="hidden" template-variable="yourid" value="{yourid}" /> <input type="hidden" template-variable="yourid" value="{yourid}" />
<input type="hidden" template-variable="theirid" value="{theirid}" /> <input type="hidden" template-variable="theirid" value="{theirid}" />
<input type="hidden" template-variable="followingCount" value="{followingCount}" /> <input type="hidden" template-variable="followingCount" value="{followingCount}" />
<script type="text/javascript" src="{relative_path}/src/forum/following.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>

@ -53,7 +53,7 @@
</footer> </footer>
<script> <script>
$.getScript(RELATIVE_PATH + '/src/forum/footer.js'); require(['forum/footer']);
</script> </script>
</body> </body>

@ -15,8 +15,14 @@
<script> <script>
require.config({ require.config({
baseUrl: "{relative_path}/src/modules", baseUrl: "{relative_path}/src/modules",
waitSeconds: 3 waitSeconds: 3,
paths: {
"forum": '../forum'
}
}); });
requirejs.onError = function(err) {
console.log(err);
}
</script> </script>
<link rel="stylesheet" type="text/css" href="{relative_path}/css/nodebb.css" /> <link rel="stylesheet" type="text/css" href="{relative_path}/css/nodebb.css" />
@ -50,9 +56,6 @@
<li class="visible-xs"> <li class="visible-xs">
<a href="/search">[[global:header.search]]</a> <a href="/search">[[global:header.search]]</a>
</li> </li>
<li class="visible-xs">
<a href="/search">Search</a>
</li>
<li> <li>
<a href="/"></a> <a href="/"></a>
</li> </li>

@ -60,5 +60,3 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/login.js"></script>

@ -49,5 +49,3 @@
</ul> </ul>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/recent.js"></script>

@ -80,5 +80,3 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/register.js"></script>

@ -26,5 +26,3 @@
<button class="btn btn-primary btn-block btn-lg" id="reset" type="submit">Reset Password</button> <button class="btn btn-primary btn-block btn-lg" id="reset" type="submit">Reset Password</button>
</form> </form>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/reset.js"></script>

@ -34,6 +34,3 @@
</form> </form>
</div> </div>
<input type="hidden" template-variable="reset_code" value="{reset_code}" /> <input type="hidden" template-variable="reset_code" value="{reset_code}" />
<script type="text/javascript" src="{relative_path}/src/forum/reset_code.js"></script>

@ -53,5 +53,3 @@
</ul> </ul>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/search.js"></script>

@ -199,7 +199,3 @@
<input type="hidden" template-variable="pinned" value="{pinned}" /> <input type="hidden" template-variable="pinned" value="{pinned}" />
<input type="hidden" template-variable="topic_name" value="{topic_name}" /> <input type="hidden" template-variable="topic_name" value="{topic_name}" />
<input type="hidden" template-variable="postcount" value="{postcount}" /> <input type="hidden" template-variable="postcount" value="{postcount}" />
<script type="text/javascript" src="{relative_path}/src/forum/topic.js"></script>

@ -54,5 +54,3 @@
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/unread.js"></script>

@ -45,5 +45,3 @@
<button id="load-more-users-btn" class="btn btn-primary">[[users:load_more]]</button> <button id="load-more-users-btn" class="btn btn-primary">[[users:load_more]]</button>
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/users.js"></script>

@ -10,6 +10,9 @@
height: 100%; height: 100%;
background: rgba(64, 64, 64, 0.6); background: rgba(64, 64, 64, 0.6);
visibility: visible; visibility: visible;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
.btn-toolbar { .btn-toolbar {
&.formatting-bar { &.formatting-bar {

@ -43,10 +43,14 @@ var async = require('async'),
name: 'redis:password', name: 'redis:password',
description: 'Password of your Redis database' description: 'Password of your Redis database'
}, { }, {
name: 'bind_address', name: "redis:database",
description: 'IP or Hostname to bind to', description: "Which database to use (0..n)",
'default': '0.0.0.0' 'default': 0
}], }, {
name: 'bind_address',
description: 'IP or Hostname to bind to',
'default': '0.0.0.0'
}],
setup: function (callback) { setup: function (callback) {
async.series([ async.series([
function (next) { function (next) {
@ -64,11 +68,13 @@ var async = require('async'),
config.redis = { config.redis = {
host: config['redis:host'], host: config['redis:host'],
port: config['redis:port'], port: config['redis:port'],
password: config['redis:password'] password: config['redis:password'],
database: config['redis:database']
}; };
delete config['redis:host']; delete config['redis:host'];
delete config['redis:port']; delete config['redis:port'];
delete config['redis:password']; delete config['redis:password'];
delete config['redis:database'];
// Add hardcoded values // Add hardcoded values
config.bcrypt_rounds = 12; config.bcrypt_rounds = 12;
@ -81,10 +87,6 @@ var async = require('async'),
protocol = urlObject.protocol, protocol = urlObject.protocol,
server_conf = config, server_conf = config,
client_conf = { client_conf = {
socket: {
address: protocol + '//' + host + (config.use_port ? ':' + config.port : '')
},
api_url: protocol + '//' + host + (config.use_port ? ':' + config.port : '') + relative_path + '/api/',
relative_path: relative_path relative_path: relative_path
}; };

@ -15,29 +15,22 @@ var RDB = require('./redis.js'),
}); });
}); });
}, },
create: function(text, score, path, uniqueId, callback) { create: function(text, path, uniqueId, callback) {
/* /**
* Score guide:
* 0 Low priority messages (probably unused)
* 5 Normal messages
* 10 High priority messages
*
* uniqueId is used solely to override stale nids. * uniqueId is used solely to override stale nids.
* If a new nid is pushed to a user and an existing nid in the user's * If a new nid is pushed to a user and an existing nid in the user's
* (un)read list contains the same uniqueId, it will be removed, and * (un)read list contains the same uniqueId, it will be removed, and
* the new one put in its place. * the new one put in its place.
*/ */
RDB.incr('notifications:next_nid', function(err, nid) { RDB.incr('notifications:next_nid', function(err, nid) {
RDB.hmset( RDB.hmset('notifications:' + nid, {
'notifications:' + nid, text: text || '',
'text', text || '', path: path || null,
'score', score || 5, datetime: Date.now(),
'path', path || null, uniqueId: uniqueId || utils.generateUUID()
'datetime', Date.now(), }, function(err, status) {
'uniqueId', uniqueId || utils.generateUUID(), if (!err) callback(nid);
function(err, status) { });
if (status === 'OK') callback(nid);
});
}); });
}, },
push: function(nid, uids, callback) { push: function(nid, uids, callback) {
@ -51,8 +44,8 @@ var RDB = require('./redis.js'),
if (parseInt(uids[x]) > 0) { if (parseInt(uids[x]) > 0) {
(function(uid) { (function(uid) {
notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() { notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.score, nid); RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid);
global.io.sockets. in ('uid_' + uid).emit('event:new_notification'); global.io.sockets.in('uid_' + uid).emit('event:new_notification');
if (callback) callback(true); if (callback) callback(true);
}); });
})(uids[x]); })(uids[x]);
@ -98,7 +91,7 @@ var RDB = require('./redis.js'),
if (parseInt(uid) > 0) { if (parseInt(uid) > 0) {
notifications.get(nid, function(notif_data) { notifications.get(nid, function(notif_data) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid); RDB.zrem('uid:' + uid + ':notifications:unread', nid);
RDB.zadd('uid:' + uid + ':notifications:read', notif_data.score, nid); RDB.zadd('uid:' + uid + ':notifications:read', notif_data.datetime, nid);
if (callback) callback(); if (callback) callback();
}); });
} }

@ -185,7 +185,7 @@ var fs = require('fs'),
} else { } else {
// Otherwise, this hook contains no methods // Otherwise, this hook contains no methods
var returnVal = (Array.isArray(args) ? args[0] : args); var returnVal = (Array.isArray(args) ? args[0] : args);
if (callback) callback(err, returnVal); if (callback) callback(null, returnVal);
} }
}, },
isActive: function(id, callback) { isActive: function(id, callback) {

@ -5,6 +5,7 @@ var RDB = require('./redis.js'),
user = require('./user.js'), user = require('./user.js'),
async = require('async'), async = require('async'),
nconf = require('nconf'), nconf = require('nconf'),
validator = require('validator'),
utils = require('../public/src/utils'), utils = require('../public/src/utils'),
plugins = require('./plugins'), plugins = require('./plugins'),
@ -92,10 +93,9 @@ var RDB = require('./redis.js'),
], function(err, results) { ], function(err, results) {
io.sockets.in('topic_' + results[0].tid).emit('event:post_edited', { io.sockets.in('topic_' + results[0].tid).emit('event:post_edited', {
pid: pid, pid: pid,
title: title, title: validator.sanitize(title).escape(),
isMainPost: results[0].isMainPost, isMainPost: results[0].isMainPost,
content: results[1] content: results[1]
}); });
}); });
}; };

@ -264,9 +264,9 @@ var RDB = require('./redis.js'),
var socketData = { var socketData = {
posts: [postData] posts: [postData]
}; };
io.sockets. in ('topic_' + tid).emit('event:new_post', socketData); io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets. in ('recent_posts').emit('event:new_post', socketData); io.sockets.in('recent_posts').emit('event:new_post', socketData);
io.sockets. in ('user/' + uid).emit('event:new_post', socketData); io.sockets.in('user/' + uid).emit('event:new_post', socketData);
}); });
callback(null, 'Reply successful'); callback(null, 'Reply successful');

@ -17,6 +17,17 @@
RedisDB.exports.auth(nconf.get('redis:password')); RedisDB.exports.auth(nconf.get('redis:password'));
} }
if( (db = nconf.get('redis:database')) ){
RedisDB.exports.select(db, function(error){
if(error !== null){
winston.err(error);
if (global.env !== 'production') {
throw new Error(error);
}
}
});
}
RedisDB.exports.handle = function(error) { RedisDB.exports.handle = function(error) {
if (error !== null) { if (error !== null) {
winston.err(error); winston.err(error);

@ -28,6 +28,7 @@ var user = require('./../user.js'),
config.minimumUsernameLength = meta.config.minimumUsernameLength; config.minimumUsernameLength = meta.config.minimumUsernameLength;
config.maximumUsernameLength = meta.config.maximumUsernameLength; config.maximumUsernameLength = meta.config.maximumUsernameLength;
config.minimumPasswordLength = meta.config.minimumPasswordLength; config.minimumPasswordLength = meta.config.minimumPasswordLength;
config.useOutgoingLinksPage = meta.config.useOutgoingLinksPage;
res.json(200, config); res.json(200, config);
}); });

@ -1,75 +0,0 @@
(function(TestBed) {
TestBed.create_routes = function(app) {
app.get('/bench/forloop', function(req, res) {
var benchData = {};
var myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
function f(x) {
return x;
}
var runCount = req.query.runs ? req.query.runs : 1000000;
function withCaching() {
var time = process.hrtime();
for (var n = 0; n < runCount; ++n) {
for (var i = 0, len = myArray.length; i < len; ++i) {
f(myArray[i]);
}
}
var diff = process.hrtime(time);
diff = diff[0] + diff[1] / 1e9;
return diff;
}
function withoutCaching() {
var time = process.hrtime();
for (var n = 0; n < runCount; ++n) {
for (var i = 0; i < myArray.length; ++i) {
f(myArray[i]);
}
}
var diff = process.hrtime(time);
diff = diff[0] + diff[1] / 1e9;
return diff;
}
function withForeach() {
var time = process.hrtime();
for (var n = 0; n < runCount; ++n) {
myArray.forEach(function(index) {
});
}
var diff = process.hrtime(time);
diff = diff[0] + diff[1] / 1e9;
return diff;
}
benchData['runs'] = runCount;
benchData['withCaching'] = withCaching();
benchData['withoutCaching'] = withoutCaching();
benchData['withForeach'] = withForeach();
res.json(benchData);
});
};
}(exports));

@ -265,28 +265,28 @@ var RDB = require('./redis.js'),
ThreadTools.get_followers = function(tid, callback) { ThreadTools.get_followers = function(tid, callback) {
RDB.smembers('tid:' + tid + ':followers', function(err, followers) { RDB.smembers('tid:' + tid + ':followers', function(err, followers) {
callback(err, followers); callback(err, followers.map(function(follower) {
return parseInt(follower, 10);
}));
}); });
} }
ThreadTools.notify_followers = function(tid, exceptUid) { ThreadTools.notify_followers = function(tid, exceptUid) {
async.parallel([ async.parallel([
function(next) { function(next) {
topics.getTopicField(tid, 'title', function(err, title) { topics.getTopicField(tid, 'title', function(err, title) {
topics.getTeaser(tid, function(err, teaser) { topics.getTeaser(tid, function(err, teaser) {
if (!err) { if (!err) {
notifications.create('<strong>' + teaser.username + '</strong> has posted a reply to: "<strong>' + title + '</strong>"', null, nconf.get('relative_path') + '/topic/' + tid, 'topic:' + tid, function(nid) { notifications.create('<strong>' + teaser.username + '</strong> has posted a reply to: "<strong>' + title + '</strong>"', nconf.get('relative_path') + '/topic/' + tid, 'topic:' + tid, function(nid) {
next(null, nid); next(null, nid);
}); });
} else next(err); } else next(err);
}); });
}); });
}, },
function(next) { function(next) {
ThreadTools.get_followers(tid, function(err, followers) { ThreadTools.get_followers(tid, function(err, followers) {
exceptUid = parseInt(exceptUid, 10);
if (followers.indexOf(exceptUid) !== -1) followers.splice(followers.indexOf(exceptUid), 1); if (followers.indexOf(exceptUid) !== -1) followers.splice(followers.indexOf(exceptUid), 1);
next(null, followers); next(null, followers);
}); });

@ -15,15 +15,18 @@ schema = require('./schema.js'),
topicSearch = reds.createSearch('nodebbtopicsearch'), topicSearch = reds.createSearch('nodebbtopicsearch'),
validator = require('validator'); validator = require('validator');
(function(Topics) { (function(Topics) {
Topics.getTopicData = function(tid, callback) { Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) { RDB.hgetall('topic:' + tid, function(err, data) {
if (err === null) if (err === null) {
if(data)
data.title = validator.sanitize(data.title).escape();
callback(data); callback(data);
else } else {
console.log(err); console.log(err);
}
}); });
} }
@ -95,7 +98,7 @@ schema = require('./schema.js'),
var timestamp = Date.now(); var timestamp = Date.now();
var args = ['topics:recent', '+inf', timestamp - 86400000, 'WITHSCORES', 'LIMIT', start, end - start + 1]; var args = ['topics:recent', '+inf', timestamp - 86400000, 'LIMIT', start, end - start + 1];
RDB.zrevrangebyscore(args, function(err, tids) { RDB.zrevrangebyscore(args, function(err, tids) {
@ -658,7 +661,6 @@ schema = require('./schema.js'),
var slug = tid + '/' + utils.slugify(title); var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now(); var timestamp = Date.now();
title = validator.sanitize(title).escape();
RDB.hmset('topic:' + tid, { RDB.hmset('topic:' + tid, {
'tid': tid, 'tid': tid,
'uid': uid, 'uid': uid,
@ -698,9 +700,9 @@ schema = require('./schema.js'),
// Notify any users looking at the category that a new topic has arrived // Notify any users looking at the category that a new topic has arrived
Topics.getTopicForCategoryView(tid, uid, function(topicData) { Topics.getTopicForCategoryView(tid, uid, function(topicData) {
io.sockets. in ('category_' + category_id).emit('event:new_topic', topicData); io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
io.sockets. in ('recent_posts').emit('event:new_topic', topicData); io.sockets.in('recent_posts').emit('event:new_topic', topicData);
io.sockets. in ('user/' + uid).emit('event:new_post', { io.sockets.in('user/' + uid).emit('event:new_post', {
posts: postData posts: postData
}); });
}); });

@ -1,193 +1,74 @@
var RDB = require('./redis.js'), var RDB = require('./redis.js'),
async = require('async'), async = require('async'),
winston = require('winston'), winston = require('winston'),
user = require('./user'), notifications = require('./notifications')
Groups = require('./groups'); Upgrade = {};
Upgrade.upgrade = function() {
function upgradeCategory(cid, callback) { winston.info('Beginning Redis database schema update');
RDB.type('categories:' + cid + ':tid', function(err, type) {
if (type === 'set') { async.series([
RDB.smembers('categories:' + cid + ':tid', function(err, tids) { function(next) {
RDB.hget('notifications:1', 'score', function(err, score) {
function moveTopic(tid, callback) { if (score) {
RDB.hget('topic:' + tid, 'timestamp', function(err, timestamp) { async.series([
if (err) function(next) {
return callback(err); RDB.keys('uid:*:notifications:flag', function(err, keys) {
if (keys.length > 0) {
RDB.zadd('temp_categories:' + cid + ':tid', timestamp, tid); winston.info('[2013/10/03] Removing deprecated Notification Flags');
callback(null); async.each(keys, function(key, next) {
}); RDB.del(key, next);
}, next);
} else {
winston.info('[2013/10/03] No Notification Flags found. Good.');
next();
}
});
},
function(next) {
winston.info('[2013/10/03] Updating Notifications');
RDB.keys('uid:*:notifications:*', function(err, keys) {
async.each(keys, function(key, next) {
RDB.zrange(key, 0, -1, function(err, nids) {
async.each(nids, function(nid, next) {
notifications.get(nid, function(notif_data) {
RDB.zadd(key, notif_data.datetime, nid, next);
});
}, next);
});
}, next);
});
},
function(next) {
RDB.keys('notifications:*', function(err, keys) {
if (keys.length > 0) {
winston.info('[2013/10/03] Removing Notification Scores');
async.each(keys, function(key, next) {
if (key === 'notifications:next_nid') return next();
RDB.hdel(key, 'score', next);
}, next);
} else {
winston.info('[2013/10/03] No Notification Scores found. Good.');
next();
}
});
}
], next);
} else {
winston.info('[2013/10/03] Updates to Notifications skipped.');
next();
} }
async.each(tids, moveTopic, function(err) {
if (!err) {
RDB.rename('temp_categories:' + cid + ':tid', 'categories:' + cid + ':tid');
callback(null);
} else
callback(err);
});
}); });
} else {
winston.info('category already upgraded ' + cid);
callback(null);
} }
}); // Add new schema updates here
} ], function(err) {
if (!err) {
function upgradeUser(uid, callback) { winston.info('Redis schema update complete!');
user.getUserFields(uid, ['joindate', 'postcount', 'reputation'], function(err, userData) { process.exit();
if (err)
return callback(err);
async.parallel([
function(next) {
if (userData.joindate)
RDB.zadd('users:joindate', userData.joindate, uid, next);
else
next(null);
},
function(next) {
if (userData.postcount)
RDB.zadd('users:postcount', userData.postcount, uid, next);
else
next(null);
},
function(next) {
if (userData.reputation)
RDB.zadd('users:reputation', userData.reputation, uid, next);
else
next(null);
}
], function(err, result) {
callback(err);
});
});
}
function upgradeUserHash(uid, callback) {
user.getUserFields(uid, ['username', 'userslug', 'email'], function(err, userData) {
if (err)
return callback(err);
async.parallel([
function(next) {
if (userData.username)
RDB.hset('username:uid', userData.username, uid, next);
else
next(null);
},
function(next) {
if (userData.userslug)
RDB.hset('userslug:uid', userData.userslug, uid, next);
else
next(null);
},
function(next) {
if (userData.email)
RDB.hset('email:uid', userData.email, uid, next);
else
next(null);
}
], function(err, result) {
callback(err);
});
});
}
function upgradeAdmins(callback) {
Groups.getGidFromName('Administrators', function(err, gid) {
if (!err && !gid) {
winston.info('Upgrading Administrators');
async.parallel([
function(next) {
RDB.smembers("administrators", next);
},
function(next) {
Groups.create('Administrators', 'Forum Administrators', next);
}
], function(err, results) {
var gid = results[1].gid;
async.each(results[0], function(uid, next) {
Groups.join(gid, uid, next);
}, callback);
});
} else { } else {
winston.info('Administrators group OK') winston.error('Errors were encountered while updating the NodeBB schema: ' + err.message);
callback();
} }
}); });
} };
exports.upgrade = function() {
winston.info('upgrading nodebb now');
var schema = [
function upgradeCategories(next) {
winston.info('upgrading categories');
RDB.lrange('categories:cid', 0, -1, function(err, cids) {
async.each(cids, upgradeCategory, function(err) {
if (!err) {
winston.info('upgraded categories');
next(null, null);
} else {
next(err, null);
}
});
});
},
function upgradeUsers(next) {
winston.info('upgrading users');
RDB.lrange('userlist', 0, -1, function(err, uids) {
async.each(uids, upgradeUser, function(err) {
if (!err) {
winston.info('upgraded users');
next(null, null);
} else {
next(err, null);
}
});
}); module.exports = Upgrade;
},
function upgradeUserHashes(next) {
winston.info('upgrading user hashes');
RDB.zrange('users:joindate', 0, -1, function(err, uids) {
if (err)
return next(err);
async.each(uids, upgradeUserHash, function(err) {
if (!err) {
winston.info('upgraded user hashes');
next(null, null);
} else {
next(err, null);
}
});
});
},
upgradeAdmins
];
async.series(schema, function(err, results) {
if (!err)
winston.info('upgrade complete');
else
winston.err(err);
process.exit();
});
}

@ -578,7 +578,7 @@ var utils = require('./../public/src/utils.js'),
topics.getTopicField(tid, 'slug', function(err, slug) { topics.getTopicField(tid, 'slug', function(err, slug) {
var message = '<strong>' + username + '</strong> made a new post'; var message = '<strong>' + username + '</strong> made a new post';
notifications.create(message, 5, nconf.get('relative_path') + '/topic/' + slug + '#' + pid, 'topic:' + tid, function(nid) { notifications.create(message, nconf.get('relative_path') + '/topic/' + slug + '#' + pid, 'topic:' + tid, function(nid) {
notifications.push(nid, followers); notifications.push(nid, followers);
}); });
}); });
@ -888,7 +888,7 @@ var utils = require('./../public/src/utils.js'),
async.parallel({ async.parallel({
unread: function(next) { unread: function(next) {
RDB.zrevrangebyscore('uid:' + uid + ':notifications:unread', 10, 0, function(err, nids) { RDB.zrevrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
// @todo handle err // @todo handle err
var unread = []; var unread = [];
@ -910,7 +910,7 @@ var utils = require('./../public/src/utils.js'),
}); });
}, },
read: function(next) { read: function(next) {
RDB.zrevrangebyscore('uid:' + uid + ':notifications:read', 10, 0, function(err, nids) { RDB.zrevrange('uid:' + uid + ':notifications:read', 0, 10, function(err, nids) {
// @todo handle err // @todo handle err
var read = []; var read = [];
@ -932,22 +932,6 @@ var utils = require('./../public/src/utils.js'),
}); });
} }
}, function(err, notifications) { }, function(err, notifications) {
// While maintaining score sorting, sort by time
var readCount = notifications.read.length,
unreadCount = notifications.unread.length;
notifications.read.sort(function(a, b) {
if (a.score === b.score) {
return (a.datetime - b.datetime) > 0 ? -1 : 1;
}
});
notifications.unread.sort(function(a, b) {
if (a.score === b.score) {
return (a.datetime - b.datetime) > 0 ? -1 : 1;
}
});
// Limit the number of notifications to `maxNotifs`, prioritising unread notifications // Limit the number of notifications to `maxNotifs`, prioritising unread notifications
if (notifications.read.length + notifications.unread.length > maxNotifs) { if (notifications.read.length + notifications.unread.length > maxNotifs) {
notifications.read.length = maxNotifs - notifications.unread.length; notifications.read.length = maxNotifs - notifications.unread.length;

@ -17,13 +17,13 @@ var express = require('express'),
admin = require('./routes/admin.js'), admin = require('./routes/admin.js'),
userRoute = require('./routes/user.js'), userRoute = require('./routes/user.js'),
apiRoute = require('./routes/api.js'), apiRoute = require('./routes/api.js'),
testBed = require('./routes/testbed.js'),
auth = require('./routes/authentication.js'), auth = require('./routes/authentication.js'),
meta = require('./meta.js'), meta = require('./meta.js'),
feed = require('./feed'), feed = require('./feed'),
plugins = require('./plugins'), plugins = require('./plugins'),
nconf = require('nconf'), nconf = require('nconf'),
winston = require('winston'); winston = require('winston'),
validator = require('validator');
(function (app) { (function (app) {
var templates = null, var templates = null,
@ -45,7 +45,7 @@ var express = require('express'),
app.build_header = function (options, callback) { app.build_header = function (options, callback) {
var defaultMetaTags = [{ var defaultMetaTags = [{
name: 'viewport', name: 'viewport',
content: 'width=device-width, initial-scale=1.0' content: 'width=device-width, initial-scale=1.0, user-scalable=no'
}, { }, {
name: 'content-type', name: 'content-type',
content: 'text/html; charset=UTF-8' content: 'text/html; charset=UTF-8'
@ -55,6 +55,9 @@ var express = require('express'),
}, { }, {
property: 'og:site_name', property: 'og:site_name',
content: meta.config.title || 'NodeBB' content: meta.config.title || 'NodeBB'
}, {
property: 'keywords',
content: meta.config['keywords'] || ''
}], }],
metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])), metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])),
templateValues = { templateValues = {
@ -131,22 +134,23 @@ var express = require('express'),
app.use(function (req, res, next) { app.use(function (req, res, next) {
res.status(404); res.status(404);
// respond with html page if (path.dirname(req.url) === '/src/forum') {
if (req.accepts('html')) { // Handle missing client-side scripts
res.type('text/javascript').send(200, '');
} else if (req.accepts('html')) {
// respond with html page
if (process.env.NODE_ENV === 'development') winston.warn('Route requested but not found: ' + req.url);
res.redirect(nconf.get('relative_path') + '/404'); res.redirect(nconf.get('relative_path') + '/404');
return; } else if (req.accepts('json')) {
} // respond with json
if (process.env.NODE_ENV === 'development') winston.warn('Route requested but not found: ' + req.url);
// respond with json res.json({
if (req.accepts('json')) {
res.send({
error: 'Not found' error: 'Not found'
}); });
return; } else {
// default to plain-text. send()
res.type('txt').send('Not found');
} }
// default to plain-text. send()
res.type('txt').send('Not found');
}); });
app.use(function (err, req, res, next) { app.use(function (err, req, res, next) {
@ -197,7 +201,6 @@ var express = require('express'),
auth.create_routes(app); auth.create_routes(app);
admin.create_routes(app); admin.create_routes(app);
userRoute.create_routes(app); userRoute.create_routes(app);
testBed.create_routes(app);
apiRoute.create_routes(app); apiRoute.create_routes(app);
@ -308,7 +311,8 @@ var express = require('express'),
}, },
function (topicData, next) { function (topicData, next) {
var lastMod = 0, var lastMod = 0,
timestamp; timestamp,
sanitize = validator.sanitize;
for (var x = 0, numPosts = topicData.posts.length; x < numPosts; x++) { for (var x = 0, numPosts = topicData.posts.length; x < numPosts; x++) {
timestamp = parseInt(topicData.posts[x].timestamp, 10); timestamp = parseInt(topicData.posts[x].timestamp, 10);
@ -321,6 +325,9 @@ var express = require('express'),
metaTags: [{ metaTags: [{
name: "title", name: "title",
content: topicData.topic_name content: topicData.topic_name
}, {
name: "description",
content: sanitize(topicData.main_posts[0].content.substr(0, 255)).escape().replace('\n', '')
}, { }, {
property: 'og:title', property: 'og:title',
content: topicData.topic_name + ' | ' + (meta.config.title || 'NodeBB') content: topicData.topic_name + ' | ' + (meta.config.title || 'NodeBB')

@ -580,7 +580,7 @@ module.exports.init = function(io) {
notifText = 'New message from <strong>' + username + '</strong>'; notifText = 'New message from <strong>' + username + '</strong>';
if (!isUserOnline(touid)) { if (!isUserOnline(touid)) {
notifications.create(notifText, 5, 'javascript:app.openChat(&apos;' + username + '&apos;, ' + uid + ');', 'notification_' + uid + '_' + touid, function(nid) { notifications.create(notifText, 'javascript:app.openChat(&apos;' + username + '&apos;, ' + uid + ');', 'notification_' + uid + '_' + touid, function(nid) {
notifications.push(nid, [touid], function(success) { notifications.push(nid, [touid], function(success) {
}); });

Loading…
Cancel
Save