From a0926d55058bfef5376fe5a2152d9472f7a13fea Mon Sep 17 00:00:00 2001 From: Mega Date: Thu, 5 Feb 2015 22:28:21 +0300 Subject: [PATCH 01/20] [ACP] Remove unnecessary panel width restriction --- public/less/admin/admin.less | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index 2e5b868048..75b194665f 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -169,7 +169,6 @@ border-radius: 3px; box-shadow: 0px 1px 3px 0px rgba(165, 165, 165, 0.75); margin-bottom: 20px; - max-width: 1000px; &.panel-default .panel-heading { background: #fefefe; @@ -232,7 +231,7 @@ } } - + #acp-search { input { background: black; @@ -250,7 +249,7 @@ width: 200px; } } - + .search-match { font-weight: 700; color: black; From 06b2a6ff68d55200dec66d050f3ad49deb83c917 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 11 Feb 2015 21:09:43 -0500 Subject: [PATCH 02/20] closes #2717 --- public/src/widgets.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/public/src/widgets.js b/public/src/widgets.js index 2200f58d5e..936ce9cd95 100644 --- a/public/src/widgets.js +++ b/public/src/widgets.js @@ -53,8 +53,7 @@ if (location === 'footer' && !$('#content [widget-area="footer"]').length) { $('#content').append($('
')); } else if (location === 'sidebar' && !$('#content [widget-area="sidebar"]').length) { - $('#content > *').wrapAll($('
')); - $('#content').append($('
')); + $('#content > *').wrapAll($('
')); } else if (location === 'header' && !$('#content [widget-area="header"]').length) { $('#content').prepend($('
')); } @@ -69,11 +68,11 @@ ajaxify.widgets.reposition(location); } - $('#content [widget-area] img:not(.user-img)').addClass('img-responsive'); + $('#content [widget-area] img:not(.user-img)').addClass('img-responsive'); } - + $(window).trigger('action:widgets.loaded', {}); - + if (typeof callback === 'function') { callback(); } From 5dfafff421ab377f466ac10056ea919b62c7e0db Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 11 Feb 2015 21:26:26 -0500 Subject: [PATCH 03/20] call callback on password update --- public/src/client/account/edit.js | 3 +++ src/socket.io/user.js | 1 + 2 files changed, 4 insertions(+) diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js index dc8bfaeaf1..86e5d1f66d 100644 --- a/public/src/client/account/edit.js +++ b/public/src/client/account/edit.js @@ -266,12 +266,15 @@ define('forum/account/edit', ['forum/account/header', 'uploader'], function(head password_confirm.on('blur', onPasswordConfirmChanged); $('#changePasswordBtn').on('click', function() { + var btn = $(this); if ((passwordvalid && passwordsmatch) || app.user.isAdmin) { + btn.addClass('disabled').find('i').removeClass('hide'); socket.emit('user.changePassword', { 'currentPassword': currentPassword.val(), 'newPassword': password.val(), 'uid': ajaxify.variables.get('theirid') }, function(err) { + btn.removeClass('disabled').find('i').addClass('hide'); currentPassword.val(''); password.val(''); password_confirm.val(''); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 4df8915994..5f844650a6 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -151,6 +151,7 @@ SocketUser.changePassword = function(socket, data, callback) { targetUid: data.uid, ip: socket.ip }); + callback(); }); }; From a62a3647a0bf74eabb5d7a64fcbe0ce3ca66575a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 11 Feb 2015 21:53:53 -0500 Subject: [PATCH 04/20] use uid, socket.uid is always 0 here --- src/socket.io/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 5f844650a6..f2af4eeccd 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -107,7 +107,7 @@ SocketUser.reset.commit = function(socket, data, callback) { }); events.log({ type: 'password-reset', - uid: socket.uid, + uid: uid, ip: socket.ip }); callback(); From f99c3a310dc0074abd2c26575e6669d93340f9ad Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 11 Feb 2015 21:54:20 -0500 Subject: [PATCH 05/20] use uid instead of socket.uid --- src/socket.io/user.js | 53 +++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 5f844650a6..fd23405758 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -84,35 +84,38 @@ SocketUser.reset.send = function(socket, email, callback) { }; SocketUser.reset.commit = function(socket, data, callback) { - if(data && data.code && data.password) { - async.series([ - async.apply(db.getObjectField, 'reset:uid', data.code), - async.apply(user.reset.commit, data.code, data.password) - ], function(err, data) { - if (err) { - return callback(err); - } + if (!data || !data.code || !data.password) { + return callback(new Error('[[error:invalid-data]]')); + } - var uid = data[0], - now = new Date(), - parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate(); + async.parallel({ + uid: async.apply(db.getObjectField, 'reset:uid', data.code), + reset: async.apply(user.reset.commit, data.code, data.password) + }, function(err, results) { + if (err) { + return callback(err); + } - user.getUserField(uid, 'username', function(err, username) { - emailer.send('reset_notify', uid, { - username: username, - date: parsedDate, - site_title: meta.config.title || 'NodeBB', - subject: '[[email:reset.notify.subject]]' - }); - }); - events.log({ - type: 'password-reset', - uid: socket.uid, - ip: socket.ip + var uid = results.uid, + now = new Date(), + parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate(); + + user.getUserField(uid, 'username', function(err, username) { + emailer.send('reset_notify', uid, { + username: username, + date: parsedDate, + site_title: meta.config.title || 'NodeBB', + subject: '[[email:reset.notify.subject]]' }); - callback(); }); - } + + events.log({ + type: 'password-reset', + uid: uid, + ip: socket.ip + }); + callback(); + }); }; SocketUser.checkStatus = function(socket, uid, callback) { From 5cebcfba7a341f2d79e014aa23557d4f6c9e305a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 12 Feb 2015 11:49:26 -0500 Subject: [PATCH 06/20] update validator ver --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8de4dbc58c..a7907757e8 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "templates.js": "^0.1.15", "uglify-js": "git+https://github.com/julianlam/UglifyJS2.git", "underscore": "~1.7.0", - "validator": "~3.28.0", + "validator": "^3.30.0", "winston": "^0.9.0", "xregexp": "~2.0.0" }, From cdd5847b399c1f51d36c5d447c75e9177efad897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 12 Feb 2015 12:23:13 -0500 Subject: [PATCH 07/20] closes #2664 --- src/user.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/user.js b/src/user.js index 035933154a..109e67a3f0 100644 --- a/src/user.js +++ b/src/user.js @@ -7,6 +7,7 @@ var async = require('async'), plugins = require('./plugins'), db = require('./database'), meta = require('./meta'), + topics = require('./topics'), groups = require('./groups'), Password = require('./password'); @@ -147,25 +148,31 @@ var async = require('async'), User.updateOnlineUsers = function(uid, callback) { callback = callback || function() {}; - db.sortedSetScore('users:online', uid, function(err, score) { - var now = Date.now(); - if (err || now - parseInt(score, 10) < 300000) { - return callback(err); - } - db.sortedSetAdd('users:online', now, uid, function(err) { - if (err) { - return callback(err); + + var now = Date.now(); + async.waterfall([ + function(next) { + db.sortedSetScore('users:online', uid, next); + }, + function(userOnlineTime, next) { + if (now - parseInt(userOnlineTime, 10) < 300000) { + return callback(); } + db.sortedSetAdd('users:online', now, uid, next); + }, + function(next) { + topics.pushUnreadCount(uid); plugins.fireHook('action:user.online', {uid: uid, timestamp: now}); - }); - }); + next(); + } + ], callback); }; User.setUserField = function(uid, field, value, callback) { callback = callback || function() {}; db.setObjectField('user:' + uid, field, value, function(err) { if (err) { - return callback(err) + return callback(err); } plugins.fireHook('action:user.set', {uid: uid, field: field, value: value, type: 'set'}); callback(); From 0421b6ef06dc01daaa8949d8cb421b53df4fa5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 12 Feb 2015 13:04:49 -0500 Subject: [PATCH 08/20] closes #2639 --- public/language/en_GB/error.json | 2 + public/language/en_GB/tags.json | 2 +- public/src/modules/composer.js | 2 + public/src/modules/composer/tags.js | 12 +- .../bootstrap-tagsinput.css | 1 + .../bootstrap-tagsinput.min.js | 511 +----------------- .../bootstrap-tagsinput.min.js.map | 1 + src/controllers/api.js | 2 + 8 files changed, 26 insertions(+), 507 deletions(-) create mode 100644 public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js.map diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 0258b28154..bf4e7229fd 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -52,6 +52,8 @@ "invalid-title": "Invalid title!", "too-many-posts": "You can only post once every %1 seconds - please wait before posting again", "too-many-posts-newbie": "As a new user, you can only post once every %1 seconds until you have earned %2 reputation - please wait before posting again", + "tag-too-short": "Please enter a longer tag. Tags should contain at least %1 characters", + "tag-too-long": "Please enter a shorter tag. Tags can't be longer than %1 characters", "file-too-big": "Maximum allowed file size is %1 kbs - please upload a smaller file", "cant-vote-self-post": "You cannot vote for your own post", diff --git a/public/language/en_GB/tags.json b/public/language/en_GB/tags.json index f67b2ca7b5..a3f75bb2e6 100644 --- a/public/language/en_GB/tags.json +++ b/public/language/en_GB/tags.json @@ -1,7 +1,7 @@ { "no_tag_topics": "There are no topics with this tag.", "tags": "Tags", - "enter_tags_here": "Enter tags here. Press enter after each tag.", + "enter_tags_here": "Enter tags here. %1-%2 characters. Press enter after each tag.", "enter_tags_here_short": "Enter tags...", "no_tags": "There are no tags yet." } \ No newline at end of file diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 088cafe260..29154d1523 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -228,6 +228,8 @@ define('composer', [ var data = { allowTopicsThumbnail: allowTopicsThumbnail, showTags: isTopic || isMain, + minimumTagLength: config.minimumTagLength, + maximumTagLength: config.maximumTagLength, isTopic: isTopic, showHandleInput: (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)) && config.allowGuestHandles, handle: composer.posts[post_uuid] ? composer.posts[post_uuid].handle || '' : undefined diff --git a/public/src/modules/composer/tags.js b/public/src/modules/composer/tags.js index 93b33fbb38..f074a5abc2 100644 --- a/public/src/modules/composer/tags.js +++ b/public/src/modules/composer/tags.js @@ -14,7 +14,17 @@ define('composer/tags', function() { tagEl.tagsinput({ maxTags: config.tagsPerTopic, - confirmKeys: [13, 44] + confirmKeys: [13, 44], + trimValue: true + }); + + tagEl.on('beforeItemAdd', function(event) { + event.cancel = event.item.length < config.minimumTagLength || event.item.length > config.maximumTagLength; + if (event.item.length < config.minimumTagLength) { + app.alertError('[[error:tag-too-short, ' + config.minimumTagLength + ']]'); + } else if (event.item.length > config.maximumTagLength) { + app.alertError('[[error:tag-too-long, ' + config.maximumTagLength + ']]'); + } }); tagEl.on('itemAdded', function(event) { diff --git a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css index 98cfa7f3c1..55f7c09df0 100644 --- a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css +++ b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css @@ -10,6 +10,7 @@ border-radius: 4px; max-width: 100%; line-height: 22px; + cursor: text; } .bootstrap-tagsinput input { border: none; diff --git a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js index aff7ea6cb8..16be0abb93 100644 --- a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js +++ b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js @@ -1,506 +1,7 @@ -(function ($) { - "use strict"; +/* + * bootstrap-tagsinput v0.4.2 by Tim Schlechter + * + */ - var defaultOptions = { - tagClass: function(item) { - return 'label label-info'; - }, - itemValue: function(item) { - return item ? item.toString() : item; - }, - itemText: function(item) { - return this.itemValue(item); - }, - freeInput: true, - maxTags: undefined, - confirmKeys: [13], - onTagExists: function(item, $tag) { - $tag.hide().fadeIn(); - } - }; - - /** - * Constructor function - */ - function TagsInput(element, options) { - this.itemsArray = []; - - this.$element = $(element); - this.$element.hide(); - - this.isSelect = (element.tagName === 'SELECT'); - this.multiple = (this.isSelect && element.hasAttribute('multiple')); - this.objectItems = options && options.itemValue; - this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : ''; - this.inputSize = Math.max(1, this.placeholderText.length); - - this.$container = $('
'); - this.$input = $('').appendTo(this.$container); - - this.$element.after(this.$container); - - this.build(options); - } - - TagsInput.prototype = { - constructor: TagsInput, - - /** - * Adds the given item as a new tag. Pass true to dontPushVal to prevent - * updating the elements val() - */ - add: function(item, dontPushVal) { - var self = this; - - if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags) - return; - - // Ignore falsey values, except false - if (item !== false && !item) - return; - - // Throw an error when trying to add an object while the itemValue option was not set - if (typeof item === "object" && !self.objectItems) - throw("Can't add objects when itemValue option is not set"); - - // Ignore strings only containg whitespace - if (item.toString().match(/^\s*$/)) - return; - - // If SELECT but not multiple, remove current tag - if (self.isSelect && !self.multiple && self.itemsArray.length > 0) - self.remove(self.itemsArray[0]); - - if (typeof item === "string" && this.$element[0].tagName === 'INPUT') { - var items = item.split(','); - if (items.length > 1) { - for (var i = 0; i < items.length; i++) { - this.add(items[i], true); - } - - if (!dontPushVal) - self.pushVal(); - return; - } - } - - var itemValue = self.options.itemValue(item), - itemText = self.options.itemText(item), - tagClass = self.options.tagClass(item); - - // Ignore items allready added - var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0]; - if (existing) { - // Invoke onTagExists - if (self.options.onTagExists) { - var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; }); - self.options.onTagExists(item, $existingTag); - } - return; - } - - // register item in internal array and map - self.itemsArray.push(item); - - // add a tag element - var $tag = $('' + htmlEncode(itemText) + ''); - $tag.data('item', item); - self.findInputWrapper().before($tag); - $tag.after(' '); - - // add