From 26df552d55d241bc1fdd6c928cef68333b4a76d1 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 6 Aug 2016 20:28:55 -0500 Subject: [PATCH 001/386] Add edit, delete, and topics:delete permissions for users acting on their own posts --- public/src/client/topic/posts.js | 4 +- src/categories/create.js | 2 +- src/privileges.js | 8 +- src/privileges/categories.js | 21 ++++ src/privileges/posts.js | 18 ++- src/privileges/topics.js | 32 +++++- src/socket.io/posts/tools.js | 12 +- src/topics/create.js | 2 + src/topics/posts.js | 4 +- src/topics/tools.js | 17 +-- src/upgrade.js | 107 +++++++++++++++++- .../admin/partials/categories/groups.tpl | 5 +- src/views/admin/partials/categories/users.tpl | 5 +- 13 files changed, 213 insertions(+), 24 deletions(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index c656b899a2..d9011d530d 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -27,7 +27,9 @@ define('forum/topic/posts', [ data.privileges = ajaxify.data.privileges; data.posts.forEach(function(post) { post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10); - post.display_moderator_tools = post.selfPost || ajaxify.data.privileges.isAdminOrMod; + post.display_edit_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_delete_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = ajaxify.data.privileges.isAdminOrMod; post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); }); diff --git a/src/categories/create.js b/src/categories/create.js index f80e78f24e..a42c260155 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -48,7 +48,7 @@ module.exports = function(Categories) { function(data, next) { category = data.category; - var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'upload:post:image']; + var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'edit', 'delete', 'upload:post:image']; async.series([ async.apply(db.setObject, 'category:' + category.cid, category), diff --git a/src/privileges.js b/src/privileges.js index 1f28a7cb91..cc1e4d48ed 100644 --- a/src/privileges.js +++ b/src/privileges.js @@ -8,6 +8,9 @@ privileges.userPrivilegeList = [ 'topics:read', 'topics:create', 'topics:reply', + 'edit', + 'delete', + 'topics:delete', 'upload:post:image', 'upload:post:file', 'purge', @@ -20,6 +23,9 @@ privileges.groupPrivilegeList = [ 'groups:topics:read', 'groups:topics:create', 'groups:topics:reply', + 'groups:edit', + 'groups:delete', + 'groups:topics:delete', 'groups:upload:post:image', 'groups:upload:post:file', 'groups:purge', @@ -33,4 +39,4 @@ require('./privileges/topics')(privileges); require('./privileges/posts')(privileges); require('./privileges/users')(privileges); -module.exports = privileges; \ No newline at end of file +module.exports = privileges; diff --git a/src/privileges/categories.js b/src/privileges/categories.js index ce2c30506e..96eaba84c4 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -23,6 +23,9 @@ module.exports = function(privileges) { {name: 'Access Topics'}, {name: 'Create Topics'}, {name: 'Reply to Topics'}, + {name: 'Edit Posts'}, + {name: 'Delete Posts'}, + {name: 'Delete Topics'}, {name: 'Upload Images'}, {name: 'Upload Files'}, {name: 'Purge'}, @@ -362,6 +365,15 @@ module.exports = function(privileges) { 'topics:reply': function(next) { groups.isMember(uid, 'cid:' + cid + ':privileges:topics:reply', next); }, + 'edit': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:edit', next); + }, + 'delete': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:delete', next); + }, + 'topics:delete': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:topics:delete', next); + }, mods: function(next) { user.isModerator(uid, cid, next); } @@ -380,6 +392,15 @@ module.exports = function(privileges) { 'groups:topics:reply': function(next) { groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:reply', next); }, + 'groups:edit': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:edit', next); + }, + 'groups:delete': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:delete', next); + }, + 'groups:topics:delete': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:delete', next); + }, 'groups:topics:read': function(next) { groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:read', next); } diff --git a/src/privileges/posts.js b/src/privileges/posts.js index a0d06f3600..c946e8c744 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -30,6 +30,7 @@ module.exports = function(privileges) { isOwner: async.apply(posts.isOwner, pids, uid), 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, cids), read: async.apply(helpers.isUserAllowedTo, 'read', uid, cids), + edit: async.apply(helpers.isUserAllowedTo, 'edit', uid, cids), }, next); } ], function(err, results) { @@ -41,7 +42,7 @@ module.exports = function(privileges) { for (var i=0; i postDeleteDuration * 1000)) { return callback(new Error('[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]')); @@ -234,10 +239,13 @@ module.exports = function(privileges) { return callback(null, {isLocked: true}); } - posts.isOwner(pid, uid, next); + async.parallel({ + owner: async.apply(posts.isOwner, pid, uid), + edit: async.apply(privileges.posts.can, 'edit', pid, uid) + }, next); }, - function(isOwner, next) { - next(null, {editable: isOwner}); + function(result, next) { + next(null, {editable: result.owner && result.edit}); } ], callback); } diff --git a/src/privileges/topics.js b/src/privileges/topics.js index d1c8958045..c54102c77e 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -22,6 +22,9 @@ module.exports = function(privileges) { async.parallel({ 'topics:reply': async.apply(helpers.isUserAllowedTo, 'topics:reply', uid, [topic.cid]), 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, [topic.cid]), + 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [topic.cid]), + edit: async.apply(helpers.isUserAllowedTo, 'edit', uid, [topic.cid]), + 'delete': async.apply(helpers.isUserAllowedTo, 'delete', uid, [topic.cid]), read: async.apply(helpers.isUserAllowedTo, 'read', uid, [topic.cid]), isOwner: function(next) { next(null, !!parseInt(uid, 10) && parseInt(uid, 10) === parseInt(topic.uid, 10)); @@ -40,7 +43,7 @@ module.exports = function(privileges) { var locked = parseInt(topic.locked, 10) === 1; var isAdminOrMod = results.isAdministrator || results.isModerator; var editable = isAdminOrMod; - var deletable = isAdminOrMod || results.isOwner; + var deletable = isAdminOrMod || (results.isOwner && results['topics:delete'][0]); plugins.fireHook('filter:privileges.topics.get', { 'topics:reply': (results['topics:reply'][0] && !locked) || isAdminOrMod, @@ -53,7 +56,9 @@ module.exports = function(privileges) { isAdminOrMod: isAdminOrMod, disabled: disabled, tid: tid, - uid: uid + uid: uid, + editOwnPosts: results.edit[0], + deleteOwnPosts: results['delete'][0] }, callback); }); }; @@ -176,6 +181,29 @@ module.exports = function(privileges) { ], callback); }; + privileges.topics.canDelete = function(tid, uid, callback) { + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return callback(err); + } + helpers.some([ + async.apply(user.isModerator, uid, cid), + async.apply(user.isAdministrator, uid), + function(next) { + async.parallel({ + owner: async.apply(topics.isOwner, tid, uid), + 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [cid]) + }, function(err, result) { + if (err) { + return next(err); + } + next(null, result.owner && result['topics:delete'][0]); + }); + } + ], callback); + }); + }; + privileges.topics.canEdit = function(tid, uid, callback) { privileges.topics.isOwnerOrAdminOrMod(tid, uid, callback); }; diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index bcb7e59642..dfc99ac2dc 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -28,6 +28,12 @@ module.exports = function(SocketPosts) { isAdminOrMod: function(next) { privileges.categories.isAdminOrMod(data.cid, socket.uid, next); }, + canEdit: function(next) { + privileges.posts.canEdit(data.pid, socket.uid, next); + }, + canDelete: function(next) { + privileges.posts.canDelete(data.pid, socket.uid, next); + }, favourited: function(next) { favourites.getFavouritesByPostIDs([data.pid], socket.uid, next); }, @@ -45,7 +51,9 @@ module.exports = function(SocketPosts) { results.posts.deleted = parseInt(results.posts.deleted, 10) === 1; results.posts.favourited = results.favourited[0]; results.posts.selfPost = socket.uid && socket.uid === parseInt(results.posts.uid, 10); - results.posts.display_moderator_tools = results.isAdminOrMod || results.posts.selfPost; + results.posts.display_edit_tools = results.canEdit; + results.posts.display_delete_tools = results.canDelete; + results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools; results.posts.display_move_tools = results.isAdminOrMod; callback(null, results); }); @@ -165,4 +173,4 @@ module.exports = function(SocketPosts) { }, callback); } -}; \ No newline at end of file +}; diff --git a/src/topics/create.js b/src/topics/create.js index a173c1d661..1b561cdcaa 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -292,6 +292,8 @@ module.exports = function(Topics) { postData.favourited = false; postData.votes = 0; + postData.display_edit_tools = true; + postData.display_delete_tools = true; postData.display_moderator_tools = true; postData.display_move_tools = true; postData.selfPost = false; diff --git a/src/topics/posts.js b/src/topics/posts.js index 6bdf103420..66fc39e926 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -141,7 +141,9 @@ module.exports = function(Topics) { var loggedIn = !!parseInt(topicPrivileges.uid, 10); topicData.posts.forEach(function(post) { if (post) { - post.display_moderator_tools = topicPrivileges.isAdminOrMod || post.selfPost; + post.display_edit_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['edit']); + post.display_delete_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['delete']); + post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0; post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || ((loggedIn || topicData.postSharing.length) && !post.deleted); post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined; diff --git a/src/topics/tools.js b/src/topics/tools.js index fd5321eb6b..3975193f76 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -1,11 +1,12 @@ 'use strict'; -var async = require('async'), +var async = require('async'); - db = require('../database'), - categories = require('../categories'), - plugins = require('../plugins'), - privileges = require('../privileges'); +var db = require('../database'); +var categories = require('../categories'); +var meta = require('../meta'); +var plugins = require('../plugins'); +var privileges = require('../privileges'); module.exports = function(Topics) { @@ -32,10 +33,10 @@ module.exports = function(Topics) { if (!exists) { return next(new Error('[[error:no-topic]]')); } - privileges.topics.isOwnerOrAdminOrMod(tid, uid, next); + privileges.topics.canDelete(tid, uid, next); }, - function (isOwnerOrAdminOrMod, next) { - if (!isOwnerOrAdminOrMod) { + function (canDelete, next) { + if (!canDelete) { return next(new Error('[[error:no-privileges]]')); } Topics.getTopicFields(tid, ['tid', 'cid', 'uid', 'deleted', 'title', 'mainPid'], next); diff --git a/src/upgrade.js b/src/upgrade.js index dc4af652c9..f30021da4e 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -10,7 +10,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2016, 7, 5); + latestSchema = Date.UTC(2016, 8, 6); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -682,6 +682,111 @@ Upgrade.upgrade = function(callback) { winston.info('[2016/08/05] Removing best posts with negative scores skipped!'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2016, 8, 6); + + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2016/08/06] Granting edit/delete/delete topic on existing categories'); + + var groupsAPI = require('./groups'); + var privilegesAPI = require('./privileges'); + + db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + async.eachSeries(cids, function(cid, next) { + privilegesAPI.categories.list(cid, function(err, data) { + var groups = data.groups; + var users = data.users; + + async.waterfall([ + function(next) { + async.eachSeries(groups, function(group, next) { + if (group.privileges['groups:topics:reply']) { + return async.parallel([ + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:edit', group.name), + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:delete', group.name) + ], function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:groups:edit, cid:' + cid + ':privileges:groups:delete granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function(next) { + async.eachSeries(groups, function(group, next) { + if (group.privileges['groups:topics:create']) { + return groupsAPI.join('cid:' + cid + ':privileges:groups:topics:delete', group.name, function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:groups:topics:delete granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function(next) { + async.eachSeries(users, function(user, next) { + if (user.privileges['topics:reply']) { + return async.parallel([ + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:edit', user.uid), + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:delete', user.uid) + ], function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:edit, cid:' + cid + ':privileges:delete granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function(next) { + async.eachSeries(users, function(user, next) { + if (user.privileges['topics:create']) { + return groupsAPI.join('cid:' + cid + ':privileges:topics:delete', user.uid, function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:topics:delete granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + } + ], function(err) { + if (!err) { + winston.info('-- cid ' + cid + ' upgraded'); + } + + next(err); + }); + }); + }, function(err) { + if (err) { + return next(err); + } + + winston.info('[2016/08/06] Granting edit/delete/delete topic on existing categories - done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2016/08/06] Granting edit/delete/delete topic on existing categories - skipped!'); + next(); + } } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!! diff --git a/src/views/admin/partials/categories/groups.tpl b/src/views/admin/partials/categories/groups.tpl index eb432bfd27..ff2558bd20 100644 --- a/src/views/admin/partials/categories/groups.tpl +++ b/src/views/admin/partials/categories/groups.tpl @@ -10,8 +10,11 @@
  • Access Topics
  • Create Topics
  • Reply to Topics
  • +
  • Edit Posts
  • +
  • Delete Posts
  • +
  • Delete Topics
  • {groups.displayName} - \ No newline at end of file + diff --git a/src/views/admin/partials/categories/users.tpl b/src/views/admin/partials/categories/users.tpl index 9ec7c262fd..c97d452d5d 100644 --- a/src/views/admin/partials/categories/users.tpl +++ b/src/views/admin/partials/categories/users.tpl @@ -10,10 +10,13 @@
  • Access Topics
  • Create Topics
  • Reply to Topics
  • +
  • Edit Posts
  • +
  • Delete Posts
  • +
  • Delete Topics
  • Moderator
  • {users.username} - \ No newline at end of file + From f62da5c4e9b0fc66ea61361d28dd70e4bf818d63 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 9 Aug 2016 09:50:49 -0500 Subject: [PATCH 002/386] see NodeBB/NodeBB#4909 --- public/src/client/topic/posts.js | 2 +- src/categories/create.js | 2 +- src/privileges.js | 8 +++---- src/privileges/categories.js | 16 +++++++------- src/privileges/posts.js | 10 ++++----- src/privileges/topics.js | 8 +++---- src/topics/posts.js | 4 ++-- src/upgrade.js | 22 +++++++++---------- .../admin/partials/categories/groups.tpl | 4 ++-- src/views/admin/partials/categories/users.tpl | 4 ++-- 10 files changed, 40 insertions(+), 40 deletions(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index d9011d530d..33be04f993 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -28,7 +28,7 @@ define('forum/topic/posts', [ data.posts.forEach(function(post) { post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10); post.display_edit_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; - post.display_delete_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_delete_tools = (ajaxify.data.privileges.deleteOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = ajaxify.data.privileges.isAdminOrMod; post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); diff --git a/src/categories/create.js b/src/categories/create.js index a42c260155..4f00da8149 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -48,7 +48,7 @@ module.exports = function(Categories) { function(data, next) { category = data.category; - var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'edit', 'delete', 'upload:post:image']; + var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'posts:edit', 'posts:delete', 'upload:post:image']; async.series([ async.apply(db.setObject, 'category:' + category.cid, category), diff --git a/src/privileges.js b/src/privileges.js index cc1e4d48ed..34b6e6fb69 100644 --- a/src/privileges.js +++ b/src/privileges.js @@ -8,8 +8,8 @@ privileges.userPrivilegeList = [ 'topics:read', 'topics:create', 'topics:reply', - 'edit', - 'delete', + 'posts:edit', + 'posts:delete', 'topics:delete', 'upload:post:image', 'upload:post:file', @@ -23,8 +23,8 @@ privileges.groupPrivilegeList = [ 'groups:topics:read', 'groups:topics:create', 'groups:topics:reply', - 'groups:edit', - 'groups:delete', + 'groups:posts:edit', + 'groups:posts:delete', 'groups:topics:delete', 'groups:upload:post:image', 'groups:upload:post:file', diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 96eaba84c4..0baf08af91 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -365,11 +365,11 @@ module.exports = function(privileges) { 'topics:reply': function(next) { groups.isMember(uid, 'cid:' + cid + ':privileges:topics:reply', next); }, - 'edit': function(next) { - groups.isMember(uid, 'cid:' + cid + ':privileges:edit', next); + 'posts:edit': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:posts:edit', next); }, - 'delete': function(next) { - groups.isMember(uid, 'cid:' + cid + ':privileges:delete', next); + 'posts:delete': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:posts:delete', next); }, 'topics:delete': function(next) { groups.isMember(uid, 'cid:' + cid + ':privileges:topics:delete', next); @@ -392,11 +392,11 @@ module.exports = function(privileges) { 'groups:topics:reply': function(next) { groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:reply', next); }, - 'groups:edit': function(next) { - groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:edit', next); + 'groups:posts:edit': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:posts:edit', next); }, - 'groups:delete': function(next) { - groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:delete', next); + 'groups:posts:delete': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:posts:delete', next); }, 'groups:topics:delete': function(next) { groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:delete', next); diff --git a/src/privileges/posts.js b/src/privileges/posts.js index c946e8c744..fe43a631c6 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -30,7 +30,7 @@ module.exports = function(privileges) { isOwner: async.apply(posts.isOwner, pids, uid), 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, cids), read: async.apply(helpers.isUserAllowedTo, 'read', uid, cids), - edit: async.apply(helpers.isUserAllowedTo, 'edit', uid, cids), + 'posts:edit': async.apply(helpers.isUserAllowedTo, 'posts:edit', uid, cids), }, next); } ], function(err, results) { @@ -42,7 +42,7 @@ module.exports = function(privileges) { for (var i=0; iAccess Topics
  • Create Topics
  • Reply to Topics
  • -
  • Edit Posts
  • -
  • Delete Posts
  • +
  • Edit Posts
  • +
  • Delete Posts
  • Delete Topics
  • diff --git a/src/views/admin/partials/categories/users.tpl b/src/views/admin/partials/categories/users.tpl index c97d452d5d..772053d5b0 100644 --- a/src/views/admin/partials/categories/users.tpl +++ b/src/views/admin/partials/categories/users.tpl @@ -10,8 +10,8 @@
  • Access Topics
  • Create Topics
  • Reply to Topics
  • -
  • Edit Posts
  • -
  • Delete Posts
  • +
  • Edit Posts
  • +
  • Delete Posts
  • Delete Topics
  • Moderator
  • From 709be213e500840a28ff2af32e54109c1dfb38bc Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 9 Aug 2016 15:35:52 -0400 Subject: [PATCH 003/386] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8523785316..8ad58b9c8c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "nodebb-plugin-spam-be-gone": "0.4.9", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.19", + "nodebb-theme-persona": "4.1.20", "nodebb-theme-vanilla": "5.1.9", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", From 9814652d764f27bc4b37527d52707206e9cb1237 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 10 Aug 2016 00:16:55 -0400 Subject: [PATCH 004/386] updating package.json to reflect latest version of NodeBB, for master branch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ad58b9c8c..fde24ac2ec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.1.0", + "version": "1.1.2", "homepage": "http://www.nodebb.org", "repository": { "type": "git", From d469d77d5e9f930fed47ef55fa5c0237b4324122 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Aug 2016 11:06:30 +0300 Subject: [PATCH 005/386] closes #4924 --- public/language/en_GB/topic.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 78769c64b5..f177725d56 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -61,7 +61,7 @@ "ignoring.description": "Do not notify me of new replies.
    Do not show topic in unread.", "thread_tools.title": "Topic Tools", - "thread_tools.markAsUnreadForAll": "Mark Unread", + "thread_tools.markAsUnreadForAll": "Mark unread for all", "thread_tools.pin": "Pin Topic", "thread_tools.unpin": "Unpin Topic", "thread_tools.lock": "Lock Topic", From f394056b7c72859843269129d98e02b8c520dcf2 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Wed, 10 Aug 2016 09:02:41 -0400 Subject: [PATCH 006/386] Latest translations and fallbacks --- public/language/ar/topic.json | 1 + public/language/bg/topic.json | 1 + public/language/bn/topic.json | 1 + public/language/cs/topic.json | 1 + public/language/da/topic.json | 1 + public/language/de/topic.json | 1 + public/language/el/topic.json | 1 + public/language/en@pirate/topic.json | 1 + public/language/en_US/topic.json | 1 + public/language/es/topic.json | 1 + public/language/et/topic.json | 1 + public/language/fa_IR/topic.json | 1 + public/language/fi/topic.json | 1 + public/language/fr/login.json | 2 +- public/language/fr/topic.json | 5 +++-- public/language/gl/topic.json | 1 + public/language/he/topic.json | 1 + public/language/hu/topic.json | 1 + public/language/id/topic.json | 1 + public/language/it/topic.json | 1 + public/language/ja/topic.json | 1 + public/language/ko/topic.json | 1 + public/language/lt/topic.json | 1 + public/language/ms/topic.json | 1 + public/language/nb/topic.json | 1 + public/language/nl/topic.json | 1 + public/language/pl/topic.json | 1 + public/language/pt_BR/topic.json | 1 + public/language/ro/topic.json | 1 + public/language/ru/topic.json | 1 + public/language/rw/topic.json | 1 + public/language/sc/topic.json | 1 + public/language/sk/topic.json | 1 + public/language/sl/topic.json | 1 + public/language/sr/topic.json | 1 + public/language/sv/topic.json | 1 + public/language/th/topic.json | 1 + public/language/tr/topic.json | 1 + public/language/vi/topic.json | 1 + public/language/zh_CN/topic.json | 1 + public/language/zh_TW/topic.json | 1 + 41 files changed, 43 insertions(+), 3 deletions(-) diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json index 9810d07d64..6ffaaa75d7 100644 --- a/public/language/ar/topic.json +++ b/public/language/ar/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "هذا الموضوع سوف ينقل إلى فئة", "fork_topic_instruction": "إضغط على المشاركات التي تريد تفريعها", "fork_no_pids": "لم تختر أي مشاركة", + "fork_pid_count": "%1 post(s) selected", "fork_success": "تم إنشاء فرع للموضوع بنجاح! إضغط هنا لمعاينة الفرع.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "أدخل عنوان موضوعك هنا...", diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json index 46f4a70313..c7853b18af 100644 --- a/public/language/bg/topic.json +++ b/public/language/bg/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Тази тема ще бъде преместена в категорията", "fork_topic_instruction": "Натиснете публикациите, които искате да отделите", "fork_no_pids": "Няма избрани публикации!", + "fork_pid_count": "Избрани публикации: %1", "fork_success": "Темата е разделена успешно! Натиснете тук, за да преминете към отделената тема.", "delete_posts_instruction": "Натиснете публикациите, които искате да изтриете/изчистите", "composer.title_placeholder": "Въведете заглавието на темата си тук...", diff --git a/public/language/bn/topic.json b/public/language/bn/topic.json index 51e88fd6e3..030f48311c 100644 --- a/public/language/bn/topic.json +++ b/public/language/bn/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "এই টপিকটি ক্যাটাগরীতে সরানো হবে", "fork_topic_instruction": "যে পোষ্টটি ফর্ক করতে চান সেটি ক্লিক করুন", "fork_no_pids": "কোন পোষ্ট সিলেক্ট করা হয় নি", + "fork_pid_count": "%1 post(s) selected", "fork_success": "টপিক ফর্ক করা হয়েছে। ফর্ক করা টপিকে যেতে এখানে ক্লিক করুন", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "আপনার টপিকের শিরোনাম দিন", diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index b528de0a8c..cc513a5dc4 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Toto téma bude přesunuto do kategorie", "fork_topic_instruction": "Vyber příspěvky, které chceš oddělit", "fork_no_pids": "Žádné příspěvky nebyly vybrány!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Zadejte název tématu...", diff --git a/public/language/da/topic.json b/public/language/da/topic.json index 22ca530310..181a58ffb7 100644 --- a/public/language/da/topic.json +++ b/public/language/da/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Denne tråd vil blive flyttet til katagorien", "fork_topic_instruction": "Klik på indlæg du ønsker at fraskille", "fork_no_pids": "Ingen indlæg valgt", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Tråden blev fraskilt! Klik her for at gå til den fraskilte tråd.", "delete_posts_instruction": "Klik på de indlæg du vil slette/rense", "composer.title_placeholder": "Angiv din trådtittel her ...", diff --git a/public/language/de/topic.json b/public/language/de/topic.json index 7d4cd8fc97..816f417397 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Dieses Thema wird verschoben nach", "fork_topic_instruction": "Klicke auf die Beiträge, die aufgespaltet werden sollen", "fork_no_pids": "Keine Beiträge ausgewählt!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Thema erfolgreich aufgespalten! Klicke hier, um zum aufgespalteten Thema zu gelangen.", "delete_posts_instruction": "Wähle die zu löschenden Beiträge aus", "composer.title_placeholder": "Hier den Titel des Themas eingeben...", diff --git a/public/language/el/topic.json b/public/language/el/topic.json index 7625226e7d..4cb37383e7 100644 --- a/public/language/el/topic.json +++ b/public/language/el/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Το θέμα θα μετακινηθεί στην κατηγορία", "fork_topic_instruction": "Κάνε κλικ στις δημοσιεύσεις που θέλεις να διαχωρίσεις", "fork_no_pids": "Δεν έχουν επιλεχθεί δημοσιεύσεις!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Εισαγωγή του τίτλου του θέματος εδώ...", diff --git a/public/language/en@pirate/topic.json b/public/language/en@pirate/topic.json index edcc8a8435..238f75eb0e 100644 --- a/public/language/en@pirate/topic.json +++ b/public/language/en@pirate/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Enter your topic title here...", diff --git a/public/language/en_US/topic.json b/public/language/en_US/topic.json index edcc8a8435..238f75eb0e 100644 --- a/public/language/en_US/topic.json +++ b/public/language/en_US/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Enter your topic title here...", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index dd3d6bb709..b080634dbb 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Este tema será movido a la categoría", "fork_topic_instruction": "Pulsa en los mensajes que quieres dividir", "fork_no_pids": "¡No has seleccionado ningún mensaje!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "¡Se ha creado un nuevo tema a partir del original! Haz click aquí para ir al nuevo tema.", "delete_posts_instruction": "Haz click en los mensajes que quieres eliminar/limpiar", "composer.title_placeholder": "Ingresa el título de tu tema...", diff --git a/public/language/et/topic.json b/public/language/et/topic.json index 622c2b6982..0e236f149d 100644 --- a/public/language/et/topic.json +++ b/public/language/et/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "See teema liigutatakse antud kategooriasse", "fork_topic_instruction": "Vajuta postitustele, mida soovid forkida", "fork_no_pids": "Sa ei ole postitusi valinud!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Edukalt ''forkisid'' teema! Vajuta siia, et vaadata loodud teemat.", "delete_posts_instruction": "Klikka postitustel, mida tahad kustutada/puhastada", "composer.title_placeholder": "Sisesta teema pealkiri siia...", diff --git a/public/language/fa_IR/topic.json b/public/language/fa_IR/topic.json index 45fca81ea5..adaf0d29f6 100644 --- a/public/language/fa_IR/topic.json +++ b/public/language/fa_IR/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "این موضوع جابه‌جا خواهد شد به دستهٔ", "fork_topic_instruction": "پست‌هایی را که می‌خواهید به موضوع تازه ببرید، انتخاب کنید", "fork_no_pids": "هیچ پستی انتخاب نشده!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "موضوع با موفقیت منشعب شد! برای رفتن به موضوع انشعابی اینجا را کلیک کنید.", "delete_posts_instruction": "با کلیک بر روی پست شما می خواهید به حذف/پاکسازی", "composer.title_placeholder": "عنوان موضوعتان را اینجا بنویسید...", diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json index b2d5e9d127..b17e0664ab 100644 --- a/public/language/fi/topic.json +++ b/public/language/fi/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Tämä keskustelu siirretään aihealueelle", "fork_topic_instruction": "Napsauta viestejä, jotka haluat haaroittaa", "fork_no_pids": "Ei valittuja viestejä!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Syötä aiheesi otsikko tähän...", diff --git a/public/language/fr/login.json b/public/language/fr/login.json index b3c80182b9..72fa0ef401 100644 --- a/public/language/fr/login.json +++ b/public/language/fr/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Identification échouée", "login_successful": "Vous êtes maintenant connecté !", "dont_have_account": "Vous n'avez pas de compte ?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Vous avez été déconnecté du Panneau de Contrôle d'Administration en raison de votre inactivité" } \ No newline at end of file diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json index dc8bb65354..10cce54b2d 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -26,8 +26,8 @@ "tools": "Outils", "flag": "Signaler", "locked": "Verrouillé", - "pinned": "Pinned", - "moved": "Moved", + "pinned": "Épinglé", + "moved": "Déplacé", "bookmark_instructions": "Cliquez ici pour retourner au dernier message lu de ce fil.", "flag_title": "Signaler ce message à la modération", "flag_success": "Ce message a bien été signalé aux modérateurs.", @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Ce sujet sera déplacé vers la catégorie", "fork_topic_instruction": "Cliquez sur les postes à scinder", "fork_no_pids": "Aucun post sélectionné !", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Sujet copié avec succès ! Cliquez ici pour aller au sujet copié.", "delete_posts_instruction": "Sélectionnez les messages que vous souhaitez supprimer/vider", "composer.title_placeholder": "Entrer le titre du sujet ici…", diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json index 96c8da628e..ab5fe853c3 100644 --- a/public/language/gl/topic.json +++ b/public/language/gl/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Este tema será movido á categoría", "fork_topic_instruction": "Fai clic nas publicacións que queiras dividir", "fork_no_pids": "Non seleccionaches ninguna publicación!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Creouse un novo tema a partir do orixinal! Fai clic aquí para ir ó novo tema.", "delete_posts_instruction": "Fai clic nas mensaxes que queres eliminar/limpar", "composer.title_placeholder": "Introduce o título do teu tema", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 8bf3fe2cda..dd0f7ad03f 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "נושא זה יועבר לקטגוריה", "fork_topic_instruction": "לחץ על הפוסטים שברצונך לשכפל", "fork_no_pids": "לא בחרת אף פוסט!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "הפוסט שוכפל בהצלחה! לחץ כאן על מנת לעבור לפוסט המשוכפל.", "delete_posts_instruction": "לחץ על הפוסטים שברצונך למחוק", "composer.title_placeholder": "הכנס את כותרת הנושא כאן...", diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json index ef9dcb52b3..47c83d78ae 100644 --- a/public/language/hu/topic.json +++ b/public/language/hu/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Ez a téma ebbe a kategóriába lesz mozgatva", "fork_topic_instruction": "Klikkelj azokra a hozzászólásokra, amiket szét akarsz szedni", "fork_no_pids": "Nincs hozzászólás kiválasztva!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Írd be a témanevet...", diff --git a/public/language/id/topic.json b/public/language/id/topic.json index 757bc1b54f..4595b97798 100644 --- a/public/language/id/topic.json +++ b/public/language/id/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Topik ini akan dipindahkan ke kategori", "fork_topic_instruction": "Klik posting yang kamu ingin cabangkan", "fork_no_pids": "Tidak ada posting yang dipilih!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Topik berhasil dicabangkan! Klik disini untuk menuju topik yang telah dicabangkan.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Masukkan judul topik di sini...", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index ccfdcec340..1834ff19cd 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Questa discussione verrà spostata nella categoria", "fork_topic_instruction": "Clicca sui post che vuoi dividere", "fork_no_pids": "Nessun post selezionato!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Topic Diviso con successo ! Clicca qui per andare al Topic Diviso.", "delete_posts_instruction": "Clicca sui post che vuoi cancellare/eliminare", "composer.title_placeholder": "Inserisci qui il titolo della discussione...", diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index 4fc666f07a..a6d8c5bbf0 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "スレッドはこちらのカテゴリへ移動", "fork_topic_instruction": "フォークしたいポストをクリックして", "fork_no_pids": "ポストが選択されていません!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "トピックをフォークするのに成功しました。ここを押して、このフォークしたトピックに行きます。", "delete_posts_instruction": "削除または粛清するには、当てはまる投稿を押してください", "composer.title_placeholder": "スレッドのタイトルを入力して...", diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json index 122525bb38..756b2d0824 100644 --- a/public/language/ko/topic.json +++ b/public/language/ko/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "이 주제를 지정한 카테고리로 이동합니다.", "fork_topic_instruction": "분리할 게시물을 선택하세요.", "fork_no_pids": "게시물이 선택되지 않았습니다.", + "fork_pid_count": "%1 post(s) selected", "fork_success": "주제가 분리되었습니다! 분리된 주제를 보려면 여기를 클릭하세요.", "delete_posts_instruction": "삭제할 게시물을 선택하세요.", "composer.title_placeholder": "여기에 제목을 입력하세요.", diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json index 4c8e07fc98..1aa37b4730 100644 --- a/public/language/lt/topic.json +++ b/public/language/lt/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Ši tema bus perkelta į kategoriją", "fork_topic_instruction": "Pažymėkite ant įrašų, kuriuos norite perkelti į naują temą", "fork_no_pids": "Nepasirinktas joks įrašas!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Sėkmingai išsišakota iš temos! Spausk čia kad nueitu į išsišakota temą", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Įrašykite temos pavadinimą...", diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json index 04b30e193d..c55222b87c 100644 --- a/public/language/ms/topic.json +++ b/public/language/ms/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Topik ini akan dipindahkan kepada kategori", "fork_topic_instruction": "Klik kiriman yang anda hendak salin", "fork_no_pids": "Tiada kiriman yang dipilih", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Berjaya menyalin topik. Klik sini untuk ke topik yang disalin.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Masukkan tajuk topik disini", diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json index fe18a3d9f9..98049be51f 100644 --- a/public/language/nb/topic.json +++ b/public/language/nb/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Dette emnet vil bli flyttet til kategorien", "fork_topic_instruction": "Trykk på innleggene du vil forgrene", "fork_no_pids": "Ingen innlegg valgt!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Dette emnet ble forgrenet! Klikk for å gå til forgrenet emne.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Skriv din tråd-tittel her", diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index 337f02bbea..8a7a93ba7e 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Dit onderwerp zal naar de categorie verplaatst worden", "fork_topic_instruction": "Klik op de berichten die afgesplitst moeten worden", "fork_no_pids": "Geen berichten geselecteerd!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Onderwerp is succesvol afgesplitst. Klik hier om het nieuwe onderwerp te zien.", "delete_posts_instruction": "Klik op de berichten die verwijderd moeten worden", "composer.title_placeholder": "Voer hier de titel van het onderwerp in...", diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 2c5cd28d2a..8c6ed0d56b 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Ten temat zostanie przeniesiony do kategorii", "fork_topic_instruction": "Zaznacz posty, które chcesz sklonować", "fork_no_pids": "Nie zaznaczyłeś żadnych postów!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Udało się skopiować temat. Kliknij tutaj, aby do niego przejść.", "delete_posts_instruction": "Kliknij na posty, które chcesz usunąć", "composer.title_placeholder": "Wpisz tutaj tytuł tematu...", diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json index 760fe42a3f..dec0d42e8e 100644 --- a/public/language/pt_BR/topic.json +++ b/public/language/pt_BR/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Este tópico será movido para a categoria", "fork_topic_instruction": "Clique nos posts que você quer ramificar", "fork_no_pids": "Nenhum post selecionado!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Tópico ramificado com sucesso! Clique aqui para ir ao tópico ramificado.", "delete_posts_instruction": "Clique nos posts que você deseja deletar/limpar", "composer.title_placeholder": "Digite aqui o título para o seu tópico...", diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json index 0daf15f7ec..0a9b94434b 100644 --- a/public/language/ro/topic.json +++ b/public/language/ro/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Acest subiect va fi mutat în categoria", "fork_topic_instruction": "Apasă pe mesajele care vrei sa le bifurci", "fork_no_pids": "Nu a fost selectat nici un mesaj!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Introdu numele subiectului aici ...", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index 9ccfff5668..0912a92c32 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Эта тема будет перенесена в категорию", "fork_topic_instruction": "Отметьте сообщения для ответвления", "fork_no_pids": "Сообщения не отмечены!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Готово! Нажмите для перехода в отделённую тему.", "delete_posts_instruction": "Отметьте сообщения, которые Вы хотите удалить/очистить", "composer.title_placeholder": "Введите название темы...", diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json index bd05fdfc3a..09f54f891f 100644 --- a/public/language/rw/topic.json +++ b/public/language/rw/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Iki kiganiro kirimurirwa mu cyiciro", "fork_topic_instruction": "Kanda ku byashizweho ushaka kugabanyaho", "fork_no_pids": "Nta kintu wahisemo!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Umaze kugabanyaho ku kiganiro! Kanda hano ugezwe ku kiganiro cyavutse. ", "delete_posts_instruction": "Kanda ku bintu ushaka guhisha/gusiba", "composer.title_placeholder": "Shyira umutwe w'ikiganiro cyawe aha...", diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json index 97fb268bc0..1cc32bab64 100644 --- a/public/language/sc/topic.json +++ b/public/language/sc/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Custa arresonada at a èssere mòvida in sa creze", "fork_topic_instruction": "Sèbera is arresonos chi boles partzire", "fork_no_pids": "Perunu arresonu seberadu!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Pone su tìtulu de s'arresonada inoghe...", diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json index 78c8473ae0..95eade8db2 100644 --- a/public/language/sk/topic.json +++ b/public/language/sk/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Táto téma bude presunutá do kategórie", "fork_topic_instruction": "Vyber príspevky, ktoré chceš oddeliť", "fork_no_pids": "Žiadne príspevky neboli vybrané!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Vlož nadpis témy sem...", diff --git a/public/language/sl/topic.json b/public/language/sl/topic.json index cc00affa99..92012a4925 100644 --- a/public/language/sl/topic.json +++ b/public/language/sl/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Ta tema bo premaknjena v to kategorijo", "fork_topic_instruction": "Klikni na objavo, ki o želiš odcepiti", "fork_no_pids": "Ni izbranih objav!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Uspešno ste razcepili temo! Klikni tu za ogled te teme.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Vpiši naslov teme...", diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json index 42b56929a3..e43df818ad 100644 --- a/public/language/sr/topic.json +++ b/public/language/sr/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "This topic will be moved to the category", "fork_topic_instruction": "Click the posts you want to fork", "fork_no_pids": "No posts selected!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "Enter your topic title here...", diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json index 734afb5719..99fad2d42b 100644 --- a/public/language/sv/topic.json +++ b/public/language/sv/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Detta ämne kommer att flyttas till kategorin", "fork_topic_instruction": "Klicka på de inlägg du vill grena", "fork_no_pids": "Inga inlägg valda!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Ämnet har blivit förgrenat. Klicka här för att gå till det förgrenade ämnet.", "delete_posts_instruction": "Klicka på inläggen du vill radera/rensa bort", "composer.title_placeholder": "Skriv in ämnets titel här...", diff --git a/public/language/th/topic.json b/public/language/th/topic.json index 532af2a8a8..fa4e0bccc4 100644 --- a/public/language/th/topic.json +++ b/public/language/th/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "กระทู้นี้จะถูกย้ายไปที่หมวดหมู่", "fork_topic_instruction": "คลิกที่โพสที่คุณต้องการที่จะแยก", "fork_no_pids": "ไม่มีโพสต์ที่เลือก!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Successfully forked topic! Click here to go to the forked topic.", "delete_posts_instruction": "Click the posts you want to delete/purge", "composer.title_placeholder": "ป้อนชื่อกระทู้ของคุณที่นี่ ...", diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index b870308c74..8854a5eab9 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Bu konu kategorisine taşınacak", "fork_topic_instruction": "Ayırmak istediğiniz iletileri tıklayın", "fork_no_pids": "Hiç bir ileti seçilmedi!", + "fork_pid_count": "%1 ileti(ler) seçildi", "fork_success": "Başlık başarıyla ayrıldı!", "delete_posts_instruction": "Silmek/temizlemek istediğiniz iletilere tıklayın.", "composer.title_placeholder": "Başlık ismini buraya girin...", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 8c7085fcbf..3035b9ac9d 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "Chủ đề này sẽ được chuyển tới phần mục", "fork_topic_instruction": "Chọn vào bài gửi mà bạn muốn fork", "fork_no_pids": "Chưa chọn bài gửi nào!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "Tạo bản sao thành công! Nhấn vào đây để chuyển tới chủ đề vừa tạo.", "delete_posts_instruction": "Chọn những bài viết bạn muốn xoá", "composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...", diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json index 63ebe02f77..072da54e1a 100644 --- a/public/language/zh_CN/topic.json +++ b/public/language/zh_CN/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "此主题将被移动到版块", "fork_topic_instruction": "点击将分割的帖子", "fork_no_pids": "未选中帖子!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "成功分割主题! 点这里跳转到分割后的主题。", "delete_posts_instruction": "点击想要删除/永久删除的帖子", "composer.title_placeholder": "在此输入您主题的标题...", diff --git a/public/language/zh_TW/topic.json b/public/language/zh_TW/topic.json index 87bb2d8e08..30d46b9419 100644 --- a/public/language/zh_TW/topic.json +++ b/public/language/zh_TW/topic.json @@ -86,6 +86,7 @@ "topic_will_be_moved_to": "這個主題將會被移動到類別", "fork_topic_instruction": "點擊要作為主題的文章", "fork_no_pids": "尚未選擇文章!", + "fork_pid_count": "%1 post(s) selected", "fork_success": "成功分叉成新的主題!點擊這裡進入新的主題。", "delete_posts_instruction": "點擊你想要刪除/清除的張貼", "composer.title_placeholder": "輸入標題...", From ab858855145c009f1928f13d9925d1db337a9d1a Mon Sep 17 00:00:00 2001 From: Aziz Khoury Date: Wed, 10 Aug 2016 10:44:48 -0400 Subject: [PATCH 007/386] nodebb-plugin-spam-be-gone@0.4.10 https://github.com/akhoury/nodebb-plugin-spam-be-gone/issues/49 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fde24ac2ec..c9ad9c178f 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "nodebb-plugin-markdown": "6.0.2", "nodebb-plugin-mentions": "1.1.3", "nodebb-plugin-soundpack-default": "0.1.6", - "nodebb-plugin-spam-be-gone": "0.4.9", + "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", "nodebb-theme-persona": "4.1.20", From 936149bb389b4788adf765389831938a68851bcf Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 10 Aug 2016 09:48:01 -0500 Subject: [PATCH 008/386] grant topics:delete by default --- src/categories/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categories/create.js b/src/categories/create.js index 4f00da8149..8dcc936ebc 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -48,7 +48,7 @@ module.exports = function(Categories) { function(data, next) { category = data.category; - var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'posts:edit', 'posts:delete', 'upload:post:image']; + var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'posts:edit', 'posts:delete', 'topics:delete', 'upload:post:image']; async.series([ async.apply(db.setObject, 'category:' + category.cid, category), From 9e7d90e314d9e8ac23027c31a7b990a854fdc0e4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 10 Aug 2016 12:29:25 -0400 Subject: [PATCH 009/386] allowing listeners to cancel an ajaxify request via ajaxify.start client-side hook --- public/src/ajaxify.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index ba6222f06c..97a52724d2 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -66,6 +66,11 @@ $(document).ready(function() { url = ajaxify.start(url); + // If any listeners alter url and set it to an empty string, abort the ajaxification + if (url === '') { + return false; + } + previousBodyClass = ajaxify.data.bodyClass; $('#footer, #content').removeClass('hide').addClass('ajaxifying'); @@ -107,9 +112,13 @@ $(document).ready(function() { ajaxify.start = function(url) { url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, '')); - $(window).trigger('action:ajaxify.start', {url: url}); + var payload = { + url: url + } - return url; + $(window).trigger('action:ajaxify.start', payload); + + return payload.url; }; ajaxify.updateHistory = function(url, quiet) { From e019eb40c4e01127d67679fcd80bfe41e571015b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 10 Aug 2016 12:50:41 -0400 Subject: [PATCH 010/386] altering ajaxify abort behaviour to look for null instead of empty string, and triggering ajaxify.end when abort occurs --- public/src/ajaxify.js | 3 ++- public/src/client/footer.js | 8 +++++--- public/src/client/topic.js | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 97a52724d2..de20693eb0 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -67,7 +67,8 @@ $(document).ready(function() { url = ajaxify.start(url); // If any listeners alter url and set it to an empty string, abort the ajaxification - if (url === '') { + if (url === null) { + $(window).trigger('action:ajaxify.end', {url: url, tpl_url: ajaxify.data.template.name, title: ajaxify.data.title}); return false; } diff --git a/public/src/client/footer.js b/public/src/client/footer.js index f1d05d4bfe..30da71aa12 100644 --- a/public/src/client/footer.js +++ b/public/src/client/footer.js @@ -50,10 +50,12 @@ define('forum/footer', ['notifications', 'chat', 'components', 'translator'], fu } $(window).on('action:ajaxify.end', function(ev, data) { - var tid = data.url.match(/^topic\/(\d+)/); + if (data.url) { + var tid = data.url.match(/^topic\/(\d+)/); - if (tid && tid[1]) { - delete unreadTopics[tid[1]]; + if (tid && tid[1]) { + delete unreadTopics[tid[1]]; + } } }); diff --git a/public/src/client/topic.js b/public/src/client/topic.js index da4e5456e8..aff00909d1 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -31,7 +31,7 @@ define('forum/topic', [ $(window).off('keydown', onKeyDown); } - if (!data.url.startsWith('topic/')) { + if (data.url && !data.url.startsWith('topic/')) { require(['search'], function(search) { if (search.topicDOM.active) { search.topicDOM.end(); From 037b901e8519a41d896b69d261ebbff1b9262a3f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Aug 2016 21:28:22 +0300 Subject: [PATCH 011/386] closes #4919 --- public/language/en_GB/error.json | 3 ++ src/privileges/topics.js | 48 ++++++++++++++++++++----------- src/views/admin/settings/post.tpl | 4 +++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index fa73ccfa8a..8dc3df4b02 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -67,6 +67,9 @@ "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has %1 reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", + "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/src/privileges/topics.js b/src/privileges/topics.js index c3a61fd317..7caffc06c3 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -3,6 +3,7 @@ var async = require('async'); +var meta = require('../meta'); var topics = require('../topics'); var user = require('../user'); var helpers = require('./helpers'); @@ -182,25 +183,40 @@ module.exports = function(privileges) { }; privileges.topics.canDelete = function(tid, uid, callback) { - topics.getTopicField(tid, 'cid', function(err, cid) { + var topicData; + async.waterfall([ + function(next) { + topics.getTopicFields(tid, ['cid', 'postcount'], next); + }, + function(_topicData, next) { + topicData = _topicData; + async.parallel({ + isModerator: async.apply(user.isModerator, uid, topicData.cid), + isAdministrator: async.apply(user.isAdministrator, uid), + isOwner: async.apply(topics.isOwner, tid, uid), + 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [topicData.cid]) + }, next); + } + ], function(err, results) { if (err) { return callback(err); } - helpers.some([ - async.apply(user.isModerator, uid, cid), - async.apply(user.isAdministrator, uid), - function(next) { - async.parallel({ - owner: async.apply(topics.isOwner, tid, uid), - 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [cid]) - }, function(err, result) { - if (err) { - return next(err); - } - next(null, result.owner && result['topics:delete'][0]); - }); - } - ], callback); + + if (results.isModerator || results.isAdministrator) { + return callback(null, true); + } + + var preventTopicDeleteAfterReplies = parseInt(meta.config.preventTopicDeleteAfterReplies, 10) || 0; + if (preventTopicDeleteAfterReplies && (topicData.postcount - 1) >= preventTopicDeleteAfterReplies) { + var langKey = preventTopicDeleteAfterReplies > 1 ? 'cant-delete-topic-has-replies' : 'cant-delete-topic-has-reply'; + return callback(new Error('[[error:' + langKey + ', ' + meta.config.preventTopicDeleteAfterReplies + ']]')); + } + + if (!results['topics:delete'][0]) { + return callback(null, false); + } + + callback(null, results.isOwner); }); }; diff --git a/src/views/admin/settings/post.tpl b/src/views/admin/settings/post.tpl index 4ac9aa3d2a..f58f524aca 100644 --- a/src/views/admin/settings/post.tpl +++ b/src/views/admin/settings/post.tpl @@ -52,6 +52,10 @@ +
    + + +
    From 73e19fa13aa7474864afd2217c7d06f86baf0543 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Aug 2016 22:53:30 +0300 Subject: [PATCH 012/386] fix app.alertError --- public/src/client/topic/postTools.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 41aa3d4ca4..85a8986a4b 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -35,7 +35,7 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator socket.emit('posts.loadPostTools', {pid: pid, cid: ajaxify.data.cid}, function(err, data) { if (err) { - return app.alertError(err); + return app.alertError(err.message); } data.posts.display_move_tools = data.posts.display_move_tools && index !== 0; @@ -384,11 +384,11 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator className: 'vote-modal', show: true }); - + dialog.on('click', function() { dialog.modal('hide'); }); - + }); }); }); From 56d325bd869a07a0c95ec9a7cffd3658a9718b68 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 10 Aug 2016 23:55:49 +0300 Subject: [PATCH 013/386] privilege fixes --- public/src/client/topic/posts.js | 4 ++-- src/posts/edit.js | 4 ++-- src/posts/tools.js | 4 ++-- src/privileges/posts.js | 34 +++++++++++++++++--------------- src/privileges/topics.js | 4 ++-- src/socket.io/posts/tools.js | 5 +++-- src/topics/create.js | 11 +++++------ src/topics/fork.js | 4 ++-- 8 files changed, 36 insertions(+), 34 deletions(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 33be04f993..b31b5fc0ed 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -27,8 +27,8 @@ define('forum/topic/posts', [ data.privileges = ajaxify.data.privileges; data.posts.forEach(function(post) { post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10); - post.display_edit_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; - post.display_delete_tools = (ajaxify.data.privileges.deleteOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_edit_tools = (ajaxify.data.privileges['posts:edit'] && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_delete_tools = (ajaxify.data.privileges['posts:delete'] && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = ajaxify.data.privileges.isAdminOrMod; post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); diff --git a/src/posts/edit.js b/src/posts/edit.js index e8af9ae2d4..e1df94aa9f 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -28,8 +28,8 @@ module.exports = function(Posts) { privileges.posts.canEdit(data.pid, data.uid, next); }, function (canEdit, next) { - if (!canEdit) { - return next(new Error('[[error:no-privileges]]')); + if (!canEdit.flag) { + return next(new Error(canEdit.message)); } Posts.getPostData(data.pid, next); }, diff --git a/src/posts/tools.js b/src/posts/tools.js index 40d150d049..e2573554e8 100644 --- a/src/posts/tools.js +++ b/src/posts/tools.js @@ -37,8 +37,8 @@ module.exports = function(Posts) { privileges.posts.canDelete(pid, uid, next); }, function (canDelete, next) { - if (!canDelete) { - return next(new Error('[[error:no-privileges]]')); + if (!canDelete.flag) { + return next(new Error(canDelete.message)); } if (isDelete) { diff --git a/src/privileges/posts.js b/src/privileges/posts.js index fe43a631c6..8ebf9e4bcb 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -147,15 +147,10 @@ module.exports = function(privileges) { return callback(err); } if (results.isAdminOrMod) { - return callback(null, true); + return callback(null, {flag: true}); } - if (results.isEditable.isLocked) { - return callback(new Error('[[error:topic-locked]]')); - } - if (results.isEditable.isEditExpired) { - return callback(new Error('[[error:post-edit-duration-expired, ' + meta.config.postEditDuration + ']]')); - } - callback(null, results.isEditable.editable); + + callback(null, results.isEditable); }); }; @@ -178,20 +173,25 @@ module.exports = function(privileges) { if (err) { return callback(err); } + if (results.isAdminOrMod) { - return callback(null, true); + return callback(null, {flag: true}); } + if (results.isLocked) { - return callback(new Error('[[error:topic-locked]]')); + return callback(null, {flag: false, message: '[[error:topic-locked]]'}); } + if (!results['posts:delete']) { - return callback(null, false); + return callback(null, {flag: false, message: '[[error:no-privileges]]'}); } + var postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10); if (postDeleteDuration && (Date.now() - parseInt(postData.timestamp, 10) > postDeleteDuration * 1000)) { - return callback(new Error('[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]')); + return callback(null, {flag: false, message: '[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]'}); } - callback(null, results.isOwner); + + callback(null, {flag: results.isOwner, message: '[[error:no-privileges]]'}); }); }; @@ -223,20 +223,22 @@ module.exports = function(privileges) { }; function isPostEditable(pid, uid, callback) { + var tid; async.waterfall([ function(next) { posts.getPostFields(pid, ['tid', 'timestamp'], next); }, function(postData, next) { + tid = postData.tid; var postEditDuration = parseInt(meta.config.postEditDuration, 10); if (postEditDuration && Date.now() - parseInt(postData.timestamp, 10) > postEditDuration * 1000) { - return callback(null, {isEditExpired: true}); + return callback(null, {flag: false, message: '[[error:post-edit-duration-expired, ' + meta.config.postEditDuration + ']]'}); } topics.isLocked(postData.tid, next); }, function(isLocked, next) { if (isLocked) { - return callback(null, {isLocked: true}); + return callback(null, {flag: false, message: '[[error:topic-locked]]'}); } async.parallel({ @@ -245,7 +247,7 @@ module.exports = function(privileges) { }, next); }, function(result, next) { - next(null, {editable: result.owner && result.edit}); + next(null, {flag: result.owner && result.edit, message: '[[error:no-privileges]]'}); } ], callback); } diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 7caffc06c3..27a9243222 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -58,8 +58,8 @@ module.exports = function(privileges) { disabled: disabled, tid: tid, uid: uid, - editOwnPosts: results['posts:edit'][0], - deleteOwnPosts: results['posts:delete'][0] + 'posts:edit': (results['posts:edit'][0] && !locked) || isAdminOrMod, + 'posts:delete': (results['posts:delete'][0] && !locked) || isAdminOrMod }, callback); }); }; diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index dfc99ac2dc..91e8dca241 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -47,12 +47,13 @@ module.exports = function(SocketPosts) { if (err) { return callback(err); } + results.posts.tools = results.tools.tools; results.posts.deleted = parseInt(results.posts.deleted, 10) === 1; results.posts.favourited = results.favourited[0]; results.posts.selfPost = socket.uid && socket.uid === parseInt(results.posts.uid, 10); - results.posts.display_edit_tools = results.canEdit; - results.posts.display_delete_tools = results.canDelete; + results.posts.display_edit_tools = results.canEdit.flag; + results.posts.display_delete_tools = results.canDelete.flag; results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools; results.posts.display_move_tools = results.isAdminOrMod; callback(null, results); diff --git a/src/topics/create.js b/src/topics/create.js index 1b561cdcaa..c79e0758f4 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -191,25 +191,24 @@ module.exports = function(Topics) { function(_cid, next) { cid = _cid; async.parallel({ - exists: async.apply(Topics.exists, tid), - locked: async.apply(Topics.isLocked, tid), + topicData: async.apply(Topics.getTopicData, tid), canReply: async.apply(privileges.topics.can, 'topics:reply', tid, uid), - isAdmin: async.apply(user.isAdministrator, uid), - isModerator: async.apply(user.isModerator, uid, cid) + isAdminOrMod: async.apply(privileges.categories.isAdminOrMod, cid, uid), }, next); }, function(results, next) { - if (!results.exists) { + if (!results.topicData) { return next(new Error('[[error:no-topic]]')); } - if (results.locked && !results.isAdmin && !results.isModerator) { + if (parseInt(results.topicData.locked, 10) === 1 && !results.isAdminOrMod) { return next(new Error('[[error:topic-locked]]')); } if (!results.canReply) { return next(new Error('[[error:no-privileges]]')); } + guestHandleValid(data, next); }, function(next) { diff --git a/src/topics/fork.js b/src/topics/fork.js index b2e16f069d..fbc9cdb54c 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -61,8 +61,8 @@ module.exports = function(Topics) { function(_tid, next) { function move(pid, next) { privileges.posts.canEdit(pid, uid, function(err, canEdit) { - if(err || !canEdit) { - return next(err); + if (err || !canEdit.flag) { + return next(err || new Error(canEdit.message)); } Topics.movePostToTopic(pid, tid, next); From d7cda83c8e88484cc5421eabc2316eb6a7bea2d7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 11 Aug 2016 00:12:15 +0300 Subject: [PATCH 014/386] fix lang key --- public/language/en_GB/error.json | 2 +- src/privileges/topics.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 8dc3df4b02..319f5005b6 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -67,7 +67,7 @@ "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", - "cant-delete-topic-has-reply": "You can't delete your topic after it has %1 reply", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 27a9243222..c9f0ec717a 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -208,8 +208,10 @@ module.exports = function(privileges) { var preventTopicDeleteAfterReplies = parseInt(meta.config.preventTopicDeleteAfterReplies, 10) || 0; if (preventTopicDeleteAfterReplies && (topicData.postcount - 1) >= preventTopicDeleteAfterReplies) { - var langKey = preventTopicDeleteAfterReplies > 1 ? 'cant-delete-topic-has-replies' : 'cant-delete-topic-has-reply'; - return callback(new Error('[[error:' + langKey + ', ' + meta.config.preventTopicDeleteAfterReplies + ']]')); + var langKey = preventTopicDeleteAfterReplies > 1 ? + '[[error:cant-delete-topic-has-replies, ' + meta.config.preventTopicDeleteAfterReplies + ']]': + '[[error:cant-delete-topic-has-reply]]'; + return callback(new Error(langKey)); } if (!results['topics:delete'][0]) { From 1d70dc0d8bcdd082dfcb83d7666676ba5f0f9687 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 11 Aug 2016 08:41:34 +0300 Subject: [PATCH 015/386] closes #4930 --- src/controllers/accounts/info.js | 22 +++++++++++++++------- src/user/data.js | 11 +++++++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/controllers/accounts/info.js b/src/controllers/accounts/info.js index 6662c60cd6..1117cb28c1 100644 --- a/src/controllers/accounts/info.js +++ b/src/controllers/accounts/info.js @@ -1,22 +1,30 @@ 'use strict'; -var async = require('async'), - _ = require('underscore'), +var async = require('async'); +var _ = require('underscore'); - user = require('../../user'), - helpers = require('../helpers'), - accountHelpers = require('./helpers'); +var user = require('../../user'); +var helpers = require('../helpers'); +var accountHelpers = require('./helpers'); var infoController = {}; infoController.get = function(req, res, next) { accountHelpers.getBaseUser(req.params.userslug, req.uid, function(err, userData) { + if (err) { + return next(err); + } + async.parallel({ ips: async.apply(user.getIPs, res.locals.uid, 4), history: async.apply(user.getModerationHistory, res.locals.uid), fields: async.apply(user.getUserFields, res.locals.uid, ['banned']) }, function(err, data) { - data = _.extend(userData, { + if (err) { + return next(err); + } + + userData = _.extend(userData, { ips: data.ips, history: data.history }, data.fields); @@ -24,7 +32,7 @@ infoController.get = function(req, res, next) { userData.title = '[[pages:account/info]]'; userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:settings]]'}]); - res.render('account/info', data); + res.render('account/info', userData); }); }); }; diff --git a/src/user/data.js b/src/user/data.js index 8e179ca119..d5a25bf7b4 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -58,14 +58,15 @@ module.exports = function(User) { if (fields.indexOf('banned') !== -1) { // Also retrieve ban expiry for these users db.sortedSetScores('users:banned:expire', uids, function(err, scores) { - users = users.map(function(userObj, idx) { + users.forEach(function(userObj, idx) { userObj.banned_until = scores[idx] || 0; userObj.banned_until_readable = scores[idx] ? new Date(scores[idx]).toString() : 'Not Banned'; }); + modifyUserData(users, fieldsToRemove, callback); }); + } else { + modifyUserData(users, fieldsToRemove, callback); } - - modifyUserData(users, fieldsToRemove, callback); }); }; @@ -104,7 +105,9 @@ module.exports = function(User) { return; } - user.username = validator.escape(user.username ? user.username.toString() : ''); + if (user.hasOwnProperty('username')) { + user.username = validator.escape(user.username ? user.username.toString() : ''); + } if (user.password) { user.password = undefined; From 973e2083299eb7e80ee1a4e6a3e920f9ea239a61 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 11 Aug 2016 09:31:10 +0300 Subject: [PATCH 016/386] added banned:expire to user hash --- src/user.js | 33 +++------------------------------ src/user/admin.js | 29 ++++++++++++++++++++++++++++- src/user/data.js | 13 +------------ 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/src/user.js b/src/user.js index e4f2eb5f55..d04b7c8cce 100644 --- a/src/user.js +++ b/src/user.js @@ -91,7 +91,7 @@ var utils = require('../public/src/utils'); User.getUsers = function(uids, uid, callback) { var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', - 'banned', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; + 'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; async.waterfall([ function (next) { @@ -118,6 +118,8 @@ var utils = require('../public/src/utils'); user.joindateISO = utils.toISOString(user.joindate); user.administrator = results.isAdmin[index]; user.banned = parseInt(user.banned, 10) === 1; + user.banned_until = parseInt(user['banned:expire'], 10) || 0; + user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned'; user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1; user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO; } @@ -258,35 +260,6 @@ var utils = require('../public/src/utils'); }); }; - User.isBanned = function(uid, callback) { - async.waterfall([ - async.apply(User.getUserField, uid, 'banned'), - function(banned, next) { - banned = parseInt(banned, 10) === 1; - if (!banned) { - return next(null, banned); - } else { - // If they are banned, see if the ban has expired - db.sortedSetScore('users:banned:expire', uid, function(err, score) { - var stillBanned = !score || Date.now() < score; - - if (!stillBanned) { - async.parallel([ - async.apply(db.sortedSetRemove.bind(db), 'users:banned:expire', uid), - async.apply(db.sortedSetRemove.bind(db), 'users:banned', uid), - async.apply(User.setUserField, uid, 'banned', 0) - ], function(err) { - next(err, false); - }); - } else { - next(err, true); - } - }); - } - } - ], callback); - }; - User.addInterstitials = function(callback) { plugins.registerHook('core', { hook: 'filter:register.interstitial', diff --git a/src/user/admin.js b/src/user/admin.js index cf09c0a4ed..e4384782ee 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -71,6 +71,7 @@ module.exports = function(User) { if (until > 0 && Date.now() < until) { tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid)); + tasks.push(async.apply(User.setUserField, uid, 'banned:expire', until)); } else { until = 0; } @@ -91,7 +92,7 @@ module.exports = function(User) { User.unban = function(uid, callback) { async.waterfall([ function (next) { - User.setUserField(uid, 'banned', 0, next); + User.setUserFields(uid, {banned: 0, 'banned:expire': 0}, next); }, function (next) { db.sortedSetsRemove(['users:banned', 'users:banned:expire'], uid, next); @@ -103,6 +104,32 @@ module.exports = function(User) { ], callback); }; + User.isBanned = function(uid, callback) { + async.waterfall([ + async.apply(User.getUserFields, uid, ['banned', 'banned:expire']), + function(userData, next) { + var banned = parseInt(userData.banned, 10) === 1; + if (!banned) { + return next(null, banned); + } + + // If they are banned, see if the ban has expired + var stillBanned = !userData['banned:expire'] || Date.now() < userData['banned:expire']; + + if (stillBanned) { + return next(null, true); + } + async.parallel([ + async.apply(db.sortedSetRemove.bind(db), 'users:banned:expire', uid), + async.apply(db.sortedSetRemove.bind(db), 'users:banned', uid), + async.apply(User.setUserFields, uid, {banned:0, 'banned:expire': 0}) + ], function(err) { + next(err, false); + }); + } + ], callback); + }; + User.resetFlags = function(uids, callback) { if (!Array.isArray(uids) || !uids.length) { return callback(); diff --git a/src/user/data.js b/src/user/data.js index d5a25bf7b4..6f2a2dd4b7 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -55,18 +55,7 @@ module.exports = function(User) { return callback(err); } - if (fields.indexOf('banned') !== -1) { - // Also retrieve ban expiry for these users - db.sortedSetScores('users:banned:expire', uids, function(err, scores) { - users.forEach(function(userObj, idx) { - userObj.banned_until = scores[idx] || 0; - userObj.banned_until_readable = scores[idx] ? new Date(scores[idx]).toString() : 'Not Banned'; - }); - modifyUserData(users, fieldsToRemove, callback); - }); - } else { - modifyUserData(users, fieldsToRemove, callback); - } + modifyUserData(users, fieldsToRemove, callback); }); }; From 73c3da1a832d151e6429b62bbc7b514e48eb2f75 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 11 Aug 2016 09:52:05 +0300 Subject: [PATCH 017/386] dont check maximum group name length if it is a privilege group --- src/groups/create.js | 6 ++++-- src/socket.io/admin/categories.js | 4 ++-- src/socket.io/admin/groups.js | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/groups/create.js b/src/groups/create.js index 353c0aca49..cf9b1eb784 100644 --- a/src/groups/create.js +++ b/src/groups/create.js @@ -9,7 +9,9 @@ var db = require('../database'); module.exports = function(Groups) { Groups.create = function(data, callback) { - var system = data.system === true || parseInt(data.system, 10) === 1 || data.name === 'administrators' || data.name === 'registered-users' || data.name === 'Global Moderators' || Groups.isPrivilegeGroup(data.name); + var system = data.system === true || parseInt(data.system, 10) === 1 || + data.name === 'administrators' || data.name === 'registered-users' || data.name === 'Global Moderators' || + Groups.isPrivilegeGroup(data.name); var groupData; var timestamp = data.timestamp || Date.now(); @@ -79,7 +81,7 @@ module.exports = function(Groups) { return callback(new Error('[[error:group-name-too-short]]')); } - if (name.length > (parseInt(meta.config.maximumGroupNameLength, 10) || 255)) { + if (!Groups.isPrivilegeGroup(data.name) && name.length > (parseInt(meta.config.maximumGroupNameLength, 10) || 255)) { return callback(new Error('[[error:group-name-too-long]]')); } diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index 8e24359e7a..ff2f8400b0 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -54,7 +54,7 @@ Categories.update = function(socket, data, callback) { }; Categories.setPrivilege = function(socket, data, callback) { - if(!data) { + if (!data) { return callback(new Error('[[error:invalid-data]]')); } @@ -72,7 +72,7 @@ Categories.getPrivilegeSettings = function(socket, cid, callback) { }; Categories.copyPrivilegesToChildren = function(socket, cid, callback) { - categories.getCategories([cid], socket.uid, function(err, categories) { + categories.getCategories([cid], socket.uid, function(err, categories) { if (err) { return callback(err); } diff --git a/src/socket.io/admin/groups.js b/src/socket.io/admin/groups.js index c912008210..0088fbc5c8 100644 --- a/src/socket.io/admin/groups.js +++ b/src/socket.io/admin/groups.js @@ -1,8 +1,9 @@ "use strict"; var async = require('async'); -var groups = require('../../groups'), - Groups = {}; +var groups = require('../../groups'); + +var Groups = {}; Groups.create = function(socket, data, callback) { if (!data) { From 6c11709cf16861dd3870c050c0c9414d80aa7bb8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 11 Aug 2016 14:22:15 +0300 Subject: [PATCH 018/386] closes #4933 --- src/groups/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups/create.js b/src/groups/create.js index cf9b1eb784..ffbaf97448 100644 --- a/src/groups/create.js +++ b/src/groups/create.js @@ -81,7 +81,7 @@ module.exports = function(Groups) { return callback(new Error('[[error:group-name-too-short]]')); } - if (!Groups.isPrivilegeGroup(data.name) && name.length > (parseInt(meta.config.maximumGroupNameLength, 10) || 255)) { + if (!Groups.isPrivilegeGroup(name) && name.length > (parseInt(meta.config.maximumGroupNameLength, 10) || 255)) { return callback(new Error('[[error:group-name-too-long]]')); } From 413517a08444ed40fcdddf7191b179c2e9de2e24 Mon Sep 17 00:00:00 2001 From: Accalia de Elementia Date: Thu, 11 Aug 2016 12:36:27 +0000 Subject: [PATCH 019/386] feat(socket.io-groups): Allow first page of members to be retrieved via websockets previously requesting the first page of members of a group failed --- src/socket.io/groups.js | 82 +++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 4e48a9649b..0b6423229e 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -1,23 +1,24 @@ "use strict"; -var async = require('async'), +var async = require('async'), groups = require('../groups'), meta = require('../meta'), user = require('../user'), + utils = require('../../../public/src/utils'), groupsController = require('../controllers/groups'), SocketGroups = {}; -SocketGroups.before = function(socket, method, data, next) { +SocketGroups.before = function (socket, method, data, next) { if (!data) { return next(new Error('[[error:invalid-data]]')); } next(); }; -SocketGroups.join = function(socket, data, callback) { +SocketGroups.join = function (socket, data, callback) { if (!parseInt(socket.uid, 10)) { return callback(new Error('[[error:invalid-uid]]')); } @@ -26,7 +27,7 @@ SocketGroups.join = function(socket, data, callback) { return callback(new Error('[[error:not-allowed]]')); } - groups.exists(data.groupName, function(err, exists) { + groups.exists(data.groupName, function (err, exists) { if (err || !exists) { return callback(err || new Error('[[error:no-group]]')); } @@ -38,7 +39,7 @@ SocketGroups.join = function(socket, data, callback) { async.parallel({ isAdmin: async.apply(user.isAdministrator, socket.uid), groupData: async.apply(groups.getGroupData, data.groupName) - }, function(err, results) { + }, function (err, results) { if (err) { return callback(err); } @@ -56,7 +57,7 @@ SocketGroups.join = function(socket, data, callback) { }); }; -SocketGroups.leave = function(socket, data, callback) { +SocketGroups.leave = function (socket, data, callback) { if (!parseInt(socket.uid, 10)) { return callback(new Error('[[error:invalid-uid]]')); } @@ -73,7 +74,7 @@ function isOwner(next) { async.parallel({ isAdmin: async.apply(user.isAdministrator, socket.uid), isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName) - }, function(err, results) { + }, function (err, results) { if (err || (!isOwner && !results.isAdmin)) { return callback(err || new Error('[[error:no-privileges]]')); } @@ -84,7 +85,7 @@ function isOwner(next) { function isInvited(next) { return function (socket, data, callback) { - groups.isInvited(socket.uid, data.groupName, function(err, invited) { + groups.isInvited(socket.uid, data.groupName, function (err, invited) { if (err || !invited) { return callback(err || new Error('[[error:not-invited]]')); } @@ -93,70 +94,70 @@ function isInvited(next) { }; } -SocketGroups.grant = isOwner(function(socket, data, callback) { +SocketGroups.grant = isOwner(function (socket, data, callback) { groups.ownership.grant(data.toUid, data.groupName, callback); }); -SocketGroups.rescind = isOwner(function(socket, data, callback) { +SocketGroups.rescind = isOwner(function (socket, data, callback) { groups.ownership.rescind(data.toUid, data.groupName, callback); }); -SocketGroups.accept = isOwner(function(socket, data, callback) { +SocketGroups.accept = isOwner(function (socket, data, callback) { groups.acceptMembership(data.groupName, data.toUid, callback); }); -SocketGroups.reject = isOwner(function(socket, data, callback) { +SocketGroups.reject = isOwner(function (socket, data, callback) { groups.rejectMembership(data.groupName, data.toUid, callback); }); -SocketGroups.acceptAll = isOwner(function(socket, data, callback) { +SocketGroups.acceptAll = isOwner(function (socket, data, callback) { acceptRejectAll(groups.acceptMembership, socket, data, callback); }); -SocketGroups.rejectAll = isOwner(function(socket, data, callback) { +SocketGroups.rejectAll = isOwner(function (socket, data, callback) { acceptRejectAll(groups.rejectMembership, socket, data, callback); }); function acceptRejectAll(method, socket, data, callback) { async.waterfall([ - function(next) { + function (next) { groups.getPending(data.groupName, next); }, - function(uids, next) { - async.each(uids, function(uid, next) { + function (uids, next) { + async.each(uids, function (uid, next) { method(data.groupName, uid, next); }, next); } ], callback); } -SocketGroups.issueInvite = isOwner(function(socket, data, callback) { +SocketGroups.issueInvite = isOwner(function (socket, data, callback) { groups.invite(data.groupName, data.toUid, callback); }); -SocketGroups.rescindInvite = isOwner(function(socket, data, callback) { +SocketGroups.rescindInvite = isOwner(function (socket, data, callback) { groups.rejectMembership(data.groupName, data.toUid, callback); }); -SocketGroups.acceptInvite = isInvited(function(socket, data, callback) { +SocketGroups.acceptInvite = isInvited(function (socket, data, callback) { groups.acceptMembership(data.groupName, socket.uid, callback); }); -SocketGroups.rejectInvite = isInvited(function(socket, data, callback) { +SocketGroups.rejectInvite = isInvited(function (socket, data, callback) { groups.rejectMembership(data.groupName, socket.uid, callback); }); -SocketGroups.update = isOwner(function(socket, data, callback) { +SocketGroups.update = isOwner(function (socket, data, callback) { groups.update(data.groupName, data.values, callback); }); -SocketGroups.kick = isOwner(function(socket, data, callback) { +SocketGroups.kick = isOwner(function (socket, data, callback) { if (socket.uid === parseInt(data.uid, 10)) { return callback(new Error('[[error:cant-kick-self]]')); } - groups.ownership.isOwner(data.uid, data.groupName, function(err, isOwner) { + groups.ownership.isOwner(data.uid, data.groupName, function (err, isOwner) { if (err) { return callback(err); } @@ -165,7 +166,7 @@ SocketGroups.kick = isOwner(function(socket, data, callback) { }); -SocketGroups.create = function(socket, data, callback) { +SocketGroups.create = function (socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); } else if (parseInt(meta.config.allowGroupCreation, 10) !== 1) { @@ -179,7 +180,7 @@ SocketGroups.create = function(socket, data, callback) { groups.create(data, callback); }; -SocketGroups.delete = function(socket, data, callback) { +SocketGroups.delete = function (socket, data, callback) { if (data.groupName === 'administrators' || data.groupName === 'registered-users' || data.groupName === 'Global Moderators') { @@ -189,7 +190,7 @@ SocketGroups.delete = function(socket, data, callback) { async.parallel({ isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName), isAdmin: async.apply(user.isAdministrator, socket.uid) - }, function(err, checks) { + }, function (err, checks) { if (err) { return callback(err); } @@ -201,12 +202,12 @@ SocketGroups.delete = function(socket, data, callback) { }); }; -SocketGroups.search = function(socket, data, callback) { +SocketGroups.search = function (socket, data, callback) { data.options = data.options || {}; if (!data.query) { var groupsPerPage = 15; - groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1, function(err, data) { + groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1, function (err, data) { callback(err, !err ? data.groups : null); }); return; @@ -215,7 +216,7 @@ SocketGroups.search = function(socket, data, callback) { groups.search(data.query, data.options || {}, callback); }; -SocketGroups.loadMore = function(socket, data, callback) { +SocketGroups.loadMore = function (socket, data, callback) { if (!data.sort || !data.after) { return callback(); } @@ -226,33 +227,36 @@ SocketGroups.loadMore = function(socket, data, callback) { groupsController.getGroupsFromSet(socket.uid, data.sort, start, stop, callback); }; -SocketGroups.searchMembers = function(socket, data, callback) { +SocketGroups.searchMembers = function (socket, data, callback) { data.uid = socket.uid; groups.searchMembers(data, callback); }; -SocketGroups.loadMoreMembers = function(socket, data, callback) { - if (!data.groupName || !parseInt(data.after, 10)) { +SocketGroups.loadMoreMembers = function (socket, data, callback) { + if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { return callback(new Error('[[error:invalid-data]]')); } data.after = parseInt(data.after, 10); - user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function(err, users) { + user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function (err, users) { if (err) { return callback(err); } - callback(null, {users: users, nextStart: data.after + 10}); + callback(null, { + users: users, + nextStart: data.after + 10 + }); }); }; SocketGroups.cover = {}; -SocketGroups.cover.update = function(socket, data, callback) { +SocketGroups.cover.update = function (socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); } - groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) { + groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) { if (err || !isOwner) { return callback(err || new Error('[[error:no-privileges]]')); } @@ -261,12 +265,12 @@ SocketGroups.cover.update = function(socket, data, callback) { }); }; -SocketGroups.cover.remove = function(socket, data, callback) { +SocketGroups.cover.remove = function (socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); } - groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) { + groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) { if (err || !isOwner) { return callback(err || new Error('[[error:no-privileges]]')); } From 574929337dff33060dece2d0c02910ac86fc7952 Mon Sep 17 00:00:00 2001 From: Accalia de Elementia Date: Thu, 11 Aug 2016 12:45:23 +0000 Subject: [PATCH 020/386] chore(whitespace): revert whitespace only changes --- src/socket.io/groups.js | 79 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 0b6423229e..3b314f7d0a 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -1,6 +1,6 @@ "use strict"; -var async = require('async'), +var async = require('async'), groups = require('../groups'), meta = require('../meta'), @@ -11,14 +11,14 @@ var async = require('async'), SocketGroups = {}; -SocketGroups.before = function (socket, method, data, next) { +SocketGroups.before = function(socket, method, data, next) { if (!data) { return next(new Error('[[error:invalid-data]]')); } next(); }; -SocketGroups.join = function (socket, data, callback) { +SocketGroups.join = function(socket, data, callback) { if (!parseInt(socket.uid, 10)) { return callback(new Error('[[error:invalid-uid]]')); } @@ -27,7 +27,7 @@ SocketGroups.join = function (socket, data, callback) { return callback(new Error('[[error:not-allowed]]')); } - groups.exists(data.groupName, function (err, exists) { + groups.exists(data.groupName, function(err, exists) { if (err || !exists) { return callback(err || new Error('[[error:no-group]]')); } @@ -39,7 +39,7 @@ SocketGroups.join = function (socket, data, callback) { async.parallel({ isAdmin: async.apply(user.isAdministrator, socket.uid), groupData: async.apply(groups.getGroupData, data.groupName) - }, function (err, results) { + }, function(err, results) { if (err) { return callback(err); } @@ -57,7 +57,7 @@ SocketGroups.join = function (socket, data, callback) { }); }; -SocketGroups.leave = function (socket, data, callback) { +SocketGroups.leave = function(socket, data, callback) { if (!parseInt(socket.uid, 10)) { return callback(new Error('[[error:invalid-uid]]')); } @@ -74,7 +74,7 @@ function isOwner(next) { async.parallel({ isAdmin: async.apply(user.isAdministrator, socket.uid), isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName) - }, function (err, results) { + }, function(err, results) { if (err || (!isOwner && !results.isAdmin)) { return callback(err || new Error('[[error:no-privileges]]')); } @@ -85,7 +85,7 @@ function isOwner(next) { function isInvited(next) { return function (socket, data, callback) { - groups.isInvited(socket.uid, data.groupName, function (err, invited) { + groups.isInvited(socket.uid, data.groupName, function(err, invited) { if (err || !invited) { return callback(err || new Error('[[error:not-invited]]')); } @@ -94,70 +94,70 @@ function isInvited(next) { }; } -SocketGroups.grant = isOwner(function (socket, data, callback) { +SocketGroups.grant = isOwner(function(socket, data, callback) { groups.ownership.grant(data.toUid, data.groupName, callback); }); -SocketGroups.rescind = isOwner(function (socket, data, callback) { +SocketGroups.rescind = isOwner(function(socket, data, callback) { groups.ownership.rescind(data.toUid, data.groupName, callback); }); -SocketGroups.accept = isOwner(function (socket, data, callback) { +SocketGroups.accept = isOwner(function(socket, data, callback) { groups.acceptMembership(data.groupName, data.toUid, callback); }); -SocketGroups.reject = isOwner(function (socket, data, callback) { +SocketGroups.reject = isOwner(function(socket, data, callback) { groups.rejectMembership(data.groupName, data.toUid, callback); }); -SocketGroups.acceptAll = isOwner(function (socket, data, callback) { +SocketGroups.acceptAll = isOwner(function(socket, data, callback) { acceptRejectAll(groups.acceptMembership, socket, data, callback); }); -SocketGroups.rejectAll = isOwner(function (socket, data, callback) { +SocketGroups.rejectAll = isOwner(function(socket, data, callback) { acceptRejectAll(groups.rejectMembership, socket, data, callback); }); function acceptRejectAll(method, socket, data, callback) { async.waterfall([ - function (next) { + function(next) { groups.getPending(data.groupName, next); }, - function (uids, next) { - async.each(uids, function (uid, next) { + function(uids, next) { + async.each(uids, function(uid, next) { method(data.groupName, uid, next); }, next); } ], callback); } -SocketGroups.issueInvite = isOwner(function (socket, data, callback) { +SocketGroups.issueInvite = isOwner(function(socket, data, callback) { groups.invite(data.groupName, data.toUid, callback); }); -SocketGroups.rescindInvite = isOwner(function (socket, data, callback) { +SocketGroups.rescindInvite = isOwner(function(socket, data, callback) { groups.rejectMembership(data.groupName, data.toUid, callback); }); -SocketGroups.acceptInvite = isInvited(function (socket, data, callback) { +SocketGroups.acceptInvite = isInvited(function(socket, data, callback) { groups.acceptMembership(data.groupName, socket.uid, callback); }); -SocketGroups.rejectInvite = isInvited(function (socket, data, callback) { +SocketGroups.rejectInvite = isInvited(function(socket, data, callback) { groups.rejectMembership(data.groupName, socket.uid, callback); }); -SocketGroups.update = isOwner(function (socket, data, callback) { +SocketGroups.update = isOwner(function(socket, data, callback) { groups.update(data.groupName, data.values, callback); }); -SocketGroups.kick = isOwner(function (socket, data, callback) { +SocketGroups.kick = isOwner(function(socket, data, callback) { if (socket.uid === parseInt(data.uid, 10)) { return callback(new Error('[[error:cant-kick-self]]')); } - groups.ownership.isOwner(data.uid, data.groupName, function (err, isOwner) { + groups.ownership.isOwner(data.uid, data.groupName, function(err, isOwner) { if (err) { return callback(err); } @@ -166,7 +166,7 @@ SocketGroups.kick = isOwner(function (socket, data, callback) { }); -SocketGroups.create = function (socket, data, callback) { +SocketGroups.create = function(socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); } else if (parseInt(meta.config.allowGroupCreation, 10) !== 1) { @@ -180,7 +180,7 @@ SocketGroups.create = function (socket, data, callback) { groups.create(data, callback); }; -SocketGroups.delete = function (socket, data, callback) { +SocketGroups.delete = function(socket, data, callback) { if (data.groupName === 'administrators' || data.groupName === 'registered-users' || data.groupName === 'Global Moderators') { @@ -190,7 +190,7 @@ SocketGroups.delete = function (socket, data, callback) { async.parallel({ isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName), isAdmin: async.apply(user.isAdministrator, socket.uid) - }, function (err, checks) { + }, function(err, checks) { if (err) { return callback(err); } @@ -202,12 +202,12 @@ SocketGroups.delete = function (socket, data, callback) { }); }; -SocketGroups.search = function (socket, data, callback) { +SocketGroups.search = function(socket, data, callback) { data.options = data.options || {}; if (!data.query) { var groupsPerPage = 15; - groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1, function (err, data) { + groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1, function(err, data) { callback(err, !err ? data.groups : null); }); return; @@ -216,7 +216,7 @@ SocketGroups.search = function (socket, data, callback) { groups.search(data.query, data.options || {}, callback); }; -SocketGroups.loadMore = function (socket, data, callback) { +SocketGroups.loadMore = function(socket, data, callback) { if (!data.sort || !data.after) { return callback(); } @@ -227,36 +227,33 @@ SocketGroups.loadMore = function (socket, data, callback) { groupsController.getGroupsFromSet(socket.uid, data.sort, start, stop, callback); }; -SocketGroups.searchMembers = function (socket, data, callback) { +SocketGroups.searchMembers = function(socket, data, callback) { data.uid = socket.uid; groups.searchMembers(data, callback); }; -SocketGroups.loadMoreMembers = function (socket, data, callback) { +SocketGroups.loadMoreMembers = function(socket, data, callback) { if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { return callback(new Error('[[error:invalid-data]]')); } data.after = parseInt(data.after, 10); - user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function (err, users) { + user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function(err, users) { if (err) { return callback(err); } - callback(null, { - users: users, - nextStart: data.after + 10 - }); + callback(null, {users: users, nextStart: data.after + 10}); }); }; SocketGroups.cover = {}; -SocketGroups.cover.update = function (socket, data, callback) { +SocketGroups.cover.update = function(socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); } - groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) { + groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) { if (err || !isOwner) { return callback(err || new Error('[[error:no-privileges]]')); } @@ -265,12 +262,12 @@ SocketGroups.cover.update = function (socket, data, callback) { }); }; -SocketGroups.cover.remove = function (socket, data, callback) { +SocketGroups.cover.remove = function(socket, data, callback) { if (!socket.uid) { return callback(new Error('[[error:no-privileges]]')); } - groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) { + groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) { if (err || !isOwner) { return callback(err || new Error('[[error:no-privileges]]')); } From 74a993ccb35df66fc02935fca564242edc290e59 Mon Sep 17 00:00:00 2001 From: Accalia de Elementia Date: Thu, 11 Aug 2016 12:57:06 +0000 Subject: [PATCH 021/386] fix: Use the correct path for utils --- src/socket.io/groups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 3b314f7d0a..7d9ce46a70 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -5,7 +5,7 @@ var async = require('async'), groups = require('../groups'), meta = require('../meta'), user = require('../user'), - utils = require('../../../public/src/utils'), + utils = require('../../public/src/utils'), groupsController = require('../controllers/groups'), SocketGroups = {}; From 528964cbe4e1470c3fa0d980dc59d730c5d3aa12 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Thu, 11 Aug 2016 09:02:29 -0400 Subject: [PATCH 022/386] Latest translations and fallbacks --- public/language/ar/error.json | 1 + public/language/bg/error.json | 1 + public/language/bn/error.json | 1 + public/language/cs/error.json | 1 + public/language/da/error.json | 1 + public/language/de/error.json | 1 + public/language/el/error.json | 1 + public/language/en@pirate/error.json | 1 + public/language/en_US/error.json | 1 + public/language/es/error.json | 1 + public/language/es/global.json | 2 +- public/language/es/topic.json | 4 ++-- public/language/et/error.json | 1 + public/language/fa_IR/error.json | 1 + public/language/fi/error.json | 1 + public/language/fr/error.json | 1 + public/language/fr/topic.json | 2 +- public/language/gl/error.json | 1 + public/language/he/error.json | 1 + public/language/hu/error.json | 1 + public/language/id/error.json | 1 + public/language/it/error.json | 1 + public/language/ja/error.json | 1 + public/language/ko/error.json | 1 + public/language/lt/error.json | 1 + public/language/ms/error.json | 1 + public/language/nb/error.json | 1 + public/language/nl/error.json | 1 + public/language/pl/category.json | 8 ++++---- public/language/pl/error.json | 21 ++++++++++---------- public/language/pl/global.json | 8 ++++---- public/language/pl/login.json | 2 +- public/language/pl/pages.json | 4 ++-- public/language/pl/register.json | 8 ++++---- public/language/pl/topic.json | 12 ++++++------ public/language/pl/user.json | 6 +++--- public/language/pt_BR/error.json | 1 + public/language/ro/error.json | 1 + public/language/ru/error.json | 1 + public/language/rw/error.json | 1 + public/language/sc/error.json | 1 + public/language/sk/error.json | 1 + public/language/sl/error.json | 1 + public/language/sr/error.json | 1 + public/language/sv/error.json | 1 + public/language/th/error.json | 1 + public/language/tr/error.json | 1 + public/language/vi/error.json | 29 ++++++++++++++-------------- public/language/vi/global.json | 4 ++-- public/language/vi/topic.json | 8 ++++---- public/language/zh_CN/error.json | 1 + public/language/zh_TW/error.json | 1 + 52 files changed, 98 insertions(+), 58 deletions(-) diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 33ff273b6b..4717871d44 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -20,6 +20,7 @@ "email-taken": "البريد الالكتروني مأخوذ", "email-not-confirmed": "عنوان بريدك الإلكتروني غير مفعل بعد. انقر هنا لتفعيله من فضلك.", "email-not-confirmed-chat": "لا يمكنك الدردشة حتى تقوم بتأكيد بريدك الإلكتروني، الرجاء إضغط هنا لتأكيد بريدك اﻹلكتروني.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "هذا المنتدى يستلزم تفعيل بريدك الإلكتروني، انقر هنا من فضلك لإدخاله.", "email-confirm-failed": "لم نستطع تفعيل بريدك الإلكتروني، المرجو المحاولة لاحقًا.", "confirm-email-already-sent": "لقد تم ارسال بريد التأكيد، الرجاء اﻹنتظار 1% دقائق لإعادة اﻹرسال", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 076b6fb878..6a3ef24696 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -20,6 +20,7 @@ "email-taken": "Е-пощата е заета", "email-not-confirmed": "Вашата е-поща все още не е потвърдена. Моля, натиснете тук, за да потвърдите е-пощата си.", "email-not-confirmed-chat": "Няма да можете да пишете в разговори, докато е-пощата Ви не бъде потвърдена. Моля, натиснете тук, за да потвърдите е-пощата си.", + "email-not-confirmed-email-sent": "Вашата е-поща все още не е потвърдена. Моля, проверете входящата си кутия за писмото за потвърждение.", "no-email-to-confirm": "Този форум изисква потвърдена е-поща. Моля, натиснете тук, за да въведете е-поща", "email-confirm-failed": "Не успяхме да потвърдим е-пощата Ви. Моля, опитайте отново по-късно.", "confirm-email-already-sent": "Е-писмото за потвърждение вече е изпратено. Моля, почакайте още %1 минута/и, преди да изпратите ново.", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 9f8be3d140..6a07599afa 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -20,6 +20,7 @@ "email-taken": "ইমেইল আগেই ব্যবহৃত", "email-not-confirmed": "আপনার ইমেইল এড্রেস নিশ্চিত করা হয় নি, নিশ্চিত করতে এখানে ক্লিক করুন।", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 086b3b9eca..7162bf5ebd 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -20,6 +20,7 @@ "email-taken": "Email je již použit", "email-not-confirmed": "Vaše emailová adresa zatím nebyla potvrzena. Kliknutím zde svůj email potvrdíte.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Potvrzovací email již byl odeslán. Vyčkejte %1 minut pokud chcete odeslat další.", diff --git a/public/language/da/error.json b/public/language/da/error.json index 6320e0e990..78e2b469c3 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -20,6 +20,7 @@ "email-taken": "Emailadresse allerede i brug", "email-not-confirmed": "Din email adresse er ikke blevet bekræftet endnu, venligst klik her for at bekrætige den.", "email-not-confirmed-chat": "Du kan ikke chatte før din email er bekræftet, klik her for at bekræfte din email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Dette forum kræver bekræftelse af din email, klik her for at indtaste en email", "email-confirm-failed": "Vi kunne ikke bekræfte din email, prøv igen senere.", "confirm-email-already-sent": "Bekræftelses email er allerede afsendt, vent venligt %1 minut(ter) for at sende endnu en.", diff --git a/public/language/de/error.json b/public/language/de/error.json index 6f7bd0e385..544aead1c3 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -20,6 +20,7 @@ "email-taken": "Die E-Mail-Adresse ist bereits vergeben", "email-not-confirmed": "Deine E-Mail wurde noch nicht bestätigt, bitte klicke hier, um deine E-Mail zu bestätigen.", "email-not-confirmed-chat": "Du kannst denn Chat erst nutzen wenn deine E-Mail bestätigt wurde, bitte klicke hier, um deine E-Mail zu bestätigen.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Dieses Forum setzt eine E-Mail-Bestätigung voraus, bitte klicke hier um eine E-Mail-Adresse einzugeben.", "email-confirm-failed": "Wir konnten deine E-Mail-Adresse nicht bestätigen, bitte versuch es später noch einmal", "confirm-email-already-sent": "Die Bestätigungsmail wurde verschickt, bitte warte %1 Minute(n) um eine Weitere zu verschicken.", diff --git a/public/language/el/error.json b/public/language/el/error.json index 45f9f6b2fb..b7f9c621ed 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -20,6 +20,7 @@ "email-taken": "Το email είναι πιασμένο", "email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 3a95ab7cc6..6a614b6de4 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -20,6 +20,7 @@ "email-taken": "Email taken", "email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index 3a95ab7cc6..6a614b6de4 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -20,6 +20,7 @@ "email-taken": "Email taken", "email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/es/error.json b/public/language/es/error.json index 723aa9beb6..0df59adbad 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -20,6 +20,7 @@ "email-taken": "Correo electrónico ocupado", "email-not-confirmed": "Su cuenta de correo electrónico no ha sido confirmada aún, por favor haga click aquí para confirmarla.", "email-not-confirmed-chat": "No puedes usar el chat hasta que confirmes tu dirección de correo electrónico, por favor haz click aquí para confirmar tu correo.", + "email-not-confirmed-email-sent": "Tu correo electrónico está sin confirmar, por favor busca el correo de confirmación en tu bandeja de entrada.", "no-email-to-confirm": "Este foro requiere confirmación de su email, por favor pulse aquí para introducir un email", "email-confirm-failed": "No se ha podido confirmar su email, por favor inténtelo de nuevo más tarde.", "confirm-email-already-sent": "El email de confirmación ya ha sido enviado, por favor espera %1 minuto(s) para enviar otro.", diff --git a/public/language/es/global.json b/public/language/es/global.json index fbe548dd54..79fd9c1a88 100644 --- a/public/language/es/global.json +++ b/public/language/es/global.json @@ -48,7 +48,7 @@ "online": "Conectado", "users": "Usuarios", "topics": "Temas", - "posts": "Posts", + "posts": "Mensajes", "best": "Mejor valorados", "upvoters": "Positivos", "upvoted": "Votado positivamente", diff --git a/public/language/es/topic.json b/public/language/es/topic.json index b080634dbb..a1bdf48ff4 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -80,13 +80,13 @@ "loading_more_posts": "Cargando más publicaciones", "move_topic": "Mover tema", "move_topics": "Mover temas", - "move_post": "Mover publicación", + "move_post": "Mover mensaje", "post_moved": "¡Publicación movida correctamente!", "fork_topic": "Dividir tema", "topic_will_be_moved_to": "Este tema será movido a la categoría", "fork_topic_instruction": "Pulsa en los mensajes que quieres dividir", "fork_no_pids": "¡No has seleccionado ningún mensaje!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 mensaje(s) seleccionados", "fork_success": "¡Se ha creado un nuevo tema a partir del original! Haz click aquí para ir al nuevo tema.", "delete_posts_instruction": "Haz click en los mensajes que quieres eliminar/limpiar", "composer.title_placeholder": "Ingresa el título de tu tema...", diff --git a/public/language/et/error.json b/public/language/et/error.json index 02713277b5..48024ec16a 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -20,6 +20,7 @@ "email-taken": "Email on võetud", "email-not-confirmed": "Su emaili aadress ei ole kinnitatud, vajuta siia et kinnitada.", "email-not-confirmed-chat": "Sõnumeid ei ole võimalik enne saata kui sinu email on kinnitatud. Kinnitamiseks vajuta siia.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "See foorum nõuab emaili kinnitust, palun vajuta siia, et sisestada email", "email-confirm-failed": "Meil ei õnnestunud sinu emaili kinnitada, proovi hiljem uuesti.", "confirm-email-already-sent": "Kinnituskiri on juba saadetud, palun oota %1 minut(it) uue kirja saatmiseks.", diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index d4661f4c8d..0b79d81e23 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -20,6 +20,7 @@ "email-taken": "این ایمیل گرفته شده است.", "email-not-confirmed": "ایمیل شما تاکنون تایید نشده است، برای تایید ایمیل خود را اینجا را کلیک کنید.", "email-not-confirmed-chat": "شما تا قبل از تایید ایمیل قادر به چت نیستید، لطفا برای تایید ایمیل خود اینجا کلیک کنید", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "ایمیل شما تایید نشده است ، لطفا برای وارد کردن ایمیل اینجا کلیک کنید", "email-confirm-failed": "سیستم موفق به تایید ایمیل شما نشد، لطفا بعدا دوباره سعی کنید", "confirm-email-already-sent": "ایمیل فعال‌سازی قبلا فرستاده شده، لطفا %1 دقیقه صبر کنید تا ایمیل دیگری بفرستید.", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index e728dcfc78..8862df7356 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -20,6 +20,7 @@ "email-taken": "Sähköpostiosoite varattu", "email-not-confirmed": "Sähköpostiasi ei ole vielä vahvistettu, ole hyvä ja napsauta tätä vahvistaaksesi sen.", "email-not-confirmed-chat": "Et voi keskustella ennen kuin sähköpostiosoitteesi on vahvistettu, ole hyvä ja paina tästä vahvistaaksesi sen.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index f62e4043ff..0a494e47ab 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -20,6 +20,7 @@ "email-taken": "Email déjà utilisé", "email-not-confirmed": "Votre adresse email n'est pas confirmée, cliquez ici pour la valider.", "email-not-confirmed-chat": "Il ne vous est pas possible d'utiliser le chat tant que votre adresse email n'a pas été vérifiée. Veuillez cliquer ici pour confirmer votre adresse email.", + "email-not-confirmed-email-sent": "Votre adresse email n'a pas encore été confirmée. Merci de vérifier l'email de confirmation dans votre boîte de reception.", "no-email-to-confirm": "Ce forum requiert une vérification de votre adresse email. Veuillez cliquer ici pour entrer une adresse.", "email-confirm-failed": "Votre adresse email n'a pas pu être vérifiée. Veuillez ré-essayer plus tard.", "confirm-email-already-sent": "L'email de confirmation a déjà été envoyé. Veuillez attendre %1 minute(s) avant de redemander un nouvel envoi.", diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json index 10cce54b2d..0a2f7ae7b3 100644 --- a/public/language/fr/topic.json +++ b/public/language/fr/topic.json @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Ce sujet sera déplacé vers la catégorie", "fork_topic_instruction": "Cliquez sur les postes à scinder", "fork_no_pids": "Aucun post sélectionné !", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 message(s) sélectionné(s)", "fork_success": "Sujet copié avec succès ! Cliquez ici pour aller au sujet copié.", "delete_posts_instruction": "Sélectionnez les messages que vous souhaitez supprimer/vider", "composer.title_placeholder": "Entrer le titre du sujet ici…", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 8944015afd..76bdf60486 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -20,6 +20,7 @@ "email-taken": "Correo en uso", "email-not-confirmed": "O teu correo aínda non está confirmado, por favor pica aquí para confirmalo.", "email-not-confirmed-chat": "Non podes charlar ata que confirmes o teu correo, por favor pica aquí para confirmalo.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Este foro require confirmación de correo, por favor pica aquí para introducir un correo.", "email-confirm-failed": "Non podemos confirmar o teu correo, por favor téntao de novo máis tarde.", "confirm-email-already-sent": "O correo de confirmación foi enviado, agarda %1 minute(s) para enviar outro.", diff --git a/public/language/he/error.json b/public/language/he/error.json index 3696f40550..7b00a4a288 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -20,6 +20,7 @@ "email-taken": "כתובת אימייל תפוסה", "email-not-confirmed": "כתובת המייל שלך עוד לא אושרה, לחץ כאן על-מנת לאשר את המייל שלך.", "email-not-confirmed-chat": "אין באפשרותך לשוחח עד שהדוא\"ל שלך יאושר, אנא לחץ כאן כדי לאשר את הדוא\"ל שלך.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "פורום זה דורש אישור בדוא\"ל, אנא לחץ כאן כדי להכניס לדואר אלקטרוני", "email-confirm-failed": "לא הצלחנו לאשר את הדוא\"ל שלך, תנסה שוב אחר כך", "confirm-email-already-sent": "מייל האישור כבר נשלח, אנא המתן %1 דקות כדי לשלוח מייל נוסף.", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index 19a14975ea..ce999c9ead 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -20,6 +20,7 @@ "email-taken": "Foglalt e-mail", "email-not-confirmed": "Az e-mail címed még nem lett ellenőrizve, kérlek kattints ide az e-mail címed ellenőrzéséhez!", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Ez a fórum e-mail megerősítést kíván, kérlek kattints ide egy cím beírásához", "email-confirm-failed": "Nem tudtuk ellenőrizni az e-mail címedet, kérlek próbálkozz később.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/id/error.json b/public/language/id/error.json index 111505a95d..822698d462 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -20,6 +20,7 @@ "email-taken": "Email sudah terdaftar", "email-not-confirmed": "Email kamu belum dikonfirmasi, klik disini untuk mengkonfirmasi email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/it/error.json b/public/language/it/error.json index 740211d54e..e2ff61bf88 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -20,6 +20,7 @@ "email-taken": "Email già esistente", "email-not-confirmed": "La tua Email deve essere ancora confermata, per favore clicca qui per confermare la tua Email.", "email-not-confirmed-chat": "Non potrai chattare finchè non avrai confermato la tua email, per favore clicca qui per farlo ora.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Questo forum richiede la conferma dell'indirizzo email, per favore clicca qui per inserirne uno", "email-confirm-failed": "Non possiamo confermare la tua email, per favore prova ancora più tardi.", "confirm-email-already-sent": "Email di conferma già inviata, per favore attendere %1 minuti per richiederne un'altra.", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index 786ca3988a..ba607d9c09 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -20,6 +20,7 @@ "email-taken": "メールアドレスは既に使われています", "email-not-confirmed": "あなたのメールアドレスはまだ確認されていません。メールアドレスを確認するためにはここをクリックしてください。", "email-not-confirmed-chat": "チャットを行うにはメールアドレスの確認を行う必要があります。メールアドレスを確認するためにはここをクリックしてください。", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "このフォーラムを利用するにはメールアドレスの確認を行う必要があります。メールアドレスを確認するためにはここをクリックしてください。", "email-confirm-failed": "メールアドレスの確認が出来ませんでした。再度お試しください。", "confirm-email-already-sent": "確認のメールは既に送信されています。再度送信するには、%1分後に再度お試しください。", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 45b2c548d8..1a0e568eb3 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -20,6 +20,7 @@ "email-taken": "이미 사용 중인 이메일입니다.", "email-not-confirmed": "아직 이메일이 인증되지 않았습니다. 여기를 누르면 인증 메일을 발송할 수 있습니다.", "email-not-confirmed-chat": "아직 이메일이 인증되지 않았습니다. 대화기능은 인증 후에 사용이 가능합니다.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "이메일 인증이 필요합니다. 이곳을 클릭하여 이메일 입력하세요.", "email-confirm-failed": "이메일 인증이 실패하였습니다. 잠시 후에 다시 시도하세요.", "confirm-email-already-sent": "인증 메일이 이미 발송되었습니다. %1 분 이후에 재 발송이 가능합니다.", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index 01c1a1d505..a1c40b2542 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -20,6 +20,7 @@ "email-taken": "El. pašto adresas jau užimtas", "email-not-confirmed": "Jūsų el. paštas nepatvirtintas, prašome paspausti čia norint jį patvirtinti.", "email-not-confirmed-chat": "Jūs negalite bendrauti, kol jūsų el.paštas nėra patvirtintas, prašome spausti čia kad aktyvuoti jūsų el.paštą", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Šis forumas reikalauja patvirtinimo el. paštu prašome spausti čia el. adreso įrašymui", "email-confirm-failed": "Negalime patvirtinti jūsų el. adreso, prašom bandyti vėliau.", "confirm-email-already-sent": "Patvirtinimo laiškas išsiųstas, prašome palaukti %1 minute(s) kad išsiųstume kita", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index f90fd5f3b3..d7b0fed05c 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -20,6 +20,7 @@ "email-taken": "Emel telah digunakan", "email-not-confirmed": "Emel anda belum disahkan lagi, sila klik sini untuk mengesahkan emel anda.", "email-not-confirmed-chat": "Anda tidak dibenarkan sembang sehingga emel disahkan, sila sahkan emel anda.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Forum ini memerlukan pengesahan emel, sila klik sini untuk memasukkan emel", "email-confirm-failed": "Kami tidak dapat memastikan emel anda, sila cuba lagi nanti", "confirm-email-already-sent": "Pengesahan emel telah dihantar, sila tunggu %1 minit() untuk menghantar yang baru.", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index 416c950043..b2df20d45c 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -20,6 +20,7 @@ "email-taken": "E-post opptatt", "email-not-confirmed": "E-posten din har ikke blitt bekreftet enda, vennligst klikk for å bekrefte din e-post.", "email-not-confirmed-chat": "Du kan ikke chatte før e-posten din er bekreftet, vennligst klikk her for å bekrefte e-postadressen.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Dette forumet krever e-postbekreftelse, vennligst klikk her for å skrive inn en e-post", "email-confirm-failed": "Vi kunne ikke bekrefte e-posten din, vennligst prøv igjen senere.", "confirm-email-already-sent": "E-post for bekreftelse er allerede sendt, vennligst vent %1 minutt(er) for å sende en til.", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index e66ba5134d..c22b6ec22c 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -20,6 +20,7 @@ "email-taken": "E-mailadres is al in gebruik", "email-not-confirmed": "Het e-mailadres van dit account is nog niet bevestigd, klik hier om je e-mailadres te bevestigen.", "email-not-confirmed-chat": "Het gebruik van chatfunctionaliteit is pas toegestaan na validatie van het e-mailadres.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Dit berichtenforum vereist bevestiging per e-mail, klik hier om een e-mailadres te registreren", "email-confirm-failed": "Helaas kon het e-mailadres niet bevestigd worden, probeer het later nog eens.", "confirm-email-already-sent": "Bevestigingsmail is zojuist al verzonden, wacht alsjeblieft %1 minuut (minuten) voordat je opnieuw een bevestigingsmail aanvraagt.", diff --git a/public/language/pl/category.json b/public/language/pl/category.json index e4053aa931..bbbdeee94c 100644 --- a/public/language/pl/category.json +++ b/public/language/pl/category.json @@ -10,10 +10,10 @@ "share_this_category": "Udostępnij tę kategorię", "watch": "Obserwuj", "ignore": "Ignoruj", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", + "watching": "Obserwowanie", + "ignoring": "Ignorowanie", + "watching.description": "Pokaż tematy jako nieprzeczytane", + "ignoring.description": "Nie pokazuj tematów jako nieprzeczytane", "watch.message": "Obserwujesz teraz uaktualnienia z tej kategorii", "ignore.message": "Ignorujesz teraz uaktualnienia z tej kategorii", "watched-categories": "Obserwowane kategorie" diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 627f7e061b..0a887ccf89 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -20,6 +20,7 @@ "email-taken": "Email zajęty", "email-not-confirmed": "Twój email nie został jeszcze potwierdzony. Proszę kliknąć tutaj by go potwierdzić.", "email-not-confirmed-chat": "Nie możesz prowadzić rozmów dopóki twój email nie zostanie potwierdzony, kliknij tutaj, aby potwierdzić swój email.", + "email-not-confirmed-email-sent": "Twój e-mail jeszcze nie został potwierdzony, proszę sprawdź swoją skrzynkę odbiorczą.", "no-email-to-confirm": "To forum wymaga weryfikacji przez email. Proszę kliknąć tutaj, aby wprowadzić adres.", "email-confirm-failed": "Nie byliśmy w stanie potwierdzić twojego email-a. Proszę spróbować później.", "confirm-email-already-sent": "Email potwierdzający został już wysłany, proszę odczekaj jeszcze %1 minut(y), aby wysłać kolejny.", @@ -30,7 +31,7 @@ "user-banned": "Użytkownik zbanowany", "user-too-new": "Przepraszamy, musisz odczekać %1 sekund(y) przed utworzeniem pierwszego posta", "blacklisted-ip": "Twój adres IP został zablokowany na tej społeczności. Jeśli uważasz to za błąd, zgłoś to administratorowi", - "ban-expiry-missing": "Please provide an end date for this ban", + "ban-expiry-missing": "Wprowadź datę końca blokady", "no-category": "Kategoria nie istnieje", "no-topic": "Temat nie istnieje", "no-post": "Post nie istnieje", @@ -47,13 +48,13 @@ "post-edit-duration-expired-hours-minutes": "Możesz edytować posty tylko przez %1 godzin(y) i %2 minut(y) po ich napisaniu", "post-edit-duration-expired-days": "Możesz edytować posty tylko przez %1 dzień (dni) po ich napisaniu", "post-edit-duration-expired-days-hours": "Możesz edytować posty tylko przez %1 dzień (dni) i %2 godzin(y) po ich napisaniu", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "post-delete-duration-expired": "Możesz kasować posty przez %1 sekund(-y) po napisaniu", + "post-delete-duration-expired-minutes": "Możesz kasować posty przez %1 minut(-y) po napisaniu", + "post-delete-duration-expired-minutes-seconds": "Możesz kasować posty przez %1 minut(-y) i %2 sekund(-y) po napisaniu", + "post-delete-duration-expired-hours": "Możesz kasować posty przez %1 godzin(-y) po napisaniu", + "post-delete-duration-expired-hours-minutes": "Możesz kasować posty przez %1 godzin(-y) i %2 minut(-y) po napisaniu", + "post-delete-duration-expired-days": "Możesz kasować posty przez %1 dni po napisaniu", + "post-delete-duration-expired-days-hours": "Możesz kasować posty przez %1 dni i %2 godzin(-y) po napisaniu", "content-too-short": "Prosimy wpisać dłuższy post. Posty powinny zawierać co najmniej %1 znaków.", "content-too-long": "Prosimy wpisać krótszy post. Posty nie mogą zawierać więcej niż %1 znaków.", "title-too-short": "Prosimy podać dłuższy tytuł. Tytuły powinny zawierać co najmniej %1 znaków.", @@ -76,7 +77,7 @@ "invalid-image-extension": "Błędne rozszerzenie pliku", "invalid-file-type": "Błędny typ pliku. Dozwolone typy to: %1", "group-name-too-short": "Nazwa grupy za krótka", - "group-name-too-long": "Group name too long", + "group-name-too-long": "Nazwa grupy jest za długa", "group-already-exists": "Grupa już istnieje", "group-name-change-not-allowed": "Nie można zmieniać nazwy tej grupy.", "group-already-member": "Już jesteś członkiem tej grupy", @@ -119,6 +120,6 @@ "not-in-room": "Użytkownik nie jest w pokoju", "no-users-in-room": "Brak użytkowników w pokoju", "cant-kick-self": "Nie możesz wyrzucić samego siebie z grupy", - "no-users-selected": "No user(s) selected", + "no-users-selected": "Nie wybrano żadnych użytkowników", "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/pl/global.json b/public/language/pl/global.json index bd9821c5b0..946993295c 100644 --- a/public/language/pl/global.json +++ b/public/language/pl/global.json @@ -50,10 +50,10 @@ "topics": "Tematy", "posts": "Posty", "best": "Najlepsze", - "upvoters": "Upvoters", - "upvoted": "Upvoted", - "downvoters": "Downvoters", - "downvoted": "Downvoted", + "upvoters": "Głosujący za", + "upvoted": "Oddane głosy za", + "downvoters": "Głosujący przeciw", + "downvoted": "Oddane głosy przeciw", "views": "wyświetleń", "reputation": "Punkty reputacji", "read_more": "czytaj więcej", diff --git a/public/language/pl/login.json b/public/language/pl/login.json index 2421563634..1bcd2c3cd5 100644 --- a/public/language/pl/login.json +++ b/public/language/pl/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Nie udało się zalogować. Spróbuj ponownie.", "login_successful": "Zostałeś pomyślnie zalogowany.", "dont_have_account": "Nie masz konta?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Zostałeś wylogowany z Panelu Administratora z powodu braku aktywności." } \ No newline at end of file diff --git a/public/language/pl/pages.json b/public/language/pl/pages.json index 9e11cb4c57..d748c3919b 100644 --- a/public/language/pl/pages.json +++ b/public/language/pl/pages.json @@ -12,7 +12,7 @@ "users/sort-posts": "Użytkownicy z największą liczbą postów", "users/sort-reputation": "Użytkownicy z najwyższą reputacją", "users/banned": "Banned Users", - "users/most-flags": "Most flagged users", + "users/most-flags": "Najczęściej oznaczani użytkownicy", "users/search": "Wyszukiwanie Użytkownków", "notifications": "Powiadomienia", "tags": "Tagi", @@ -29,7 +29,7 @@ "account/edit/password": "Editing password of \"%1\"", "account/edit/username": "Editing username of \"%1\"", "account/edit/email": "Editing email of \"%1\"", - "account/info": "Account Info", + "account/info": "Informacje o koncie", "account/following": "Obserwowani przez %1", "account/followers": "Obserwujący %1", "account/posts": "Posty napisane przez %1", diff --git a/public/language/pl/register.json b/public/language/pl/register.json index fee052540f..49c89fb6ac 100644 --- a/public/language/pl/register.json +++ b/public/language/pl/register.json @@ -1,6 +1,6 @@ { "register": "Rejestracja", - "cancel_registration": "Cancel Registration", + "cancel_registration": "Anuluj rejestrację", "help.email": "Domyślnie twój adres e-mail będzie ukryty.", "help.username_restrictions": "Unikalna nazwa użytkownika z min. %1 i maks. %2 znaków. Inni użytkownicy mogą ciebie zawołać pisząc @nazwa użytkownika.", "help.minimum_password_length": "Hasło musi mieć co najmniej %1 znaków.", @@ -16,8 +16,8 @@ "alternative_registration": "Alternatywna rejestracja", "terms_of_use": "Warunki korzystania z serwisu", "agree_to_terms_of_use": "Zgadzam się na powyższe warunki", - "terms_of_use_error": "You must agree to the Terms of Use", + "terms_of_use_error": "Musisz zaakceptować Terms of Use", "registration-added-to-queue": "Twoja rejestracja została dodana do kolejki oczekujących na akceptację. Otrzymasz email, kiedy zostanie zatwierdzona przez administratora.", - "interstitial.intro": "We require some additional information before we can create your account.", - "interstitial.errors-found": "We could not complete your registration:" + "interstitial.intro": "Potrzebujemy dodatkowych informacji zanim przejdziemy dalej do utworzenia Twojego konta.", + "interstitial.errors-found": "Nie mogliśmy ukończyć procesu rejestracji:" } \ No newline at end of file diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json index 8c6ed0d56b..c74aa085be 100644 --- a/public/language/pl/topic.json +++ b/public/language/pl/topic.json @@ -26,14 +26,14 @@ "tools": "Narzędzia", "flag": "Zgłoś", "locked": "Zablokowany", - "pinned": "Pinned", - "moved": "Moved", + "pinned": "Przypięte", + "moved": "Przeniesione", "bookmark_instructions": "Click here to return to the last read post in this thread.", "flag_title": "Zgłoś post do moderacji", "flag_success": "Ten post został oznaczony do moderacji.", "deleted_message": "Ten temat został skasowany. Tylko użytkownicy z uprawnieniami do zarządzania mogą go zobaczyć.", "following_topic.message": "Będziesz od teraz otrzymywał powiadomienia, gdy ktoś odpowie w tym temacie.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Zobaczysz ten temat na liście nieprzeczytanych, ale nie otrzymasz żadnego powiadomienia dotyczącego tego tematu.", "ignoring_topic.message": "Nie zobaczysz już tego tematu na liście nieprzeczytanych. Otrzymasz powiadomienie, kiedy zostaniesz wspomniany lub ktoś odda głos na twój post.", "login_to_subscribe": "Zaloguj się, aby subskrybować ten temat.", "markAsUnreadForAll.success": "Temat oznaczony jako nieprzeczytany dla wszystkich.", @@ -60,7 +60,7 @@ "thread_tools.move_all": "Przenieś wszystko", "thread_tools.fork": "Skopiuj Temat", "thread_tools.delete": "Usuń Temat", - "thread_tools.delete-posts": "Delete Posts", + "thread_tools.delete-posts": "Usuń posty", "thread_tools.delete_confirm": "Na pewno chcesz skasować ten temat?", "thread_tools.restore": "Przywróć Temat", "thread_tools.restore_confirm": "Na pewno chcesz przywrócić ten temat?", @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Ten temat zostanie przeniesiony do kategorii", "fork_topic_instruction": "Zaznacz posty, które chcesz sklonować", "fork_no_pids": "Nie zaznaczyłeś żadnych postów!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "wybrano %1 post(-ów)", "fork_success": "Udało się skopiować temat. Kliknij tutaj, aby do niego przejść.", "delete_posts_instruction": "Kliknij na posty, które chcesz usunąć", "composer.title_placeholder": "Wpisz tutaj tytuł tematu...", @@ -118,5 +118,5 @@ "link_back": "Re: [%1](%2)", "spam": "Spam", "offensive": "Obrażliwy", - "custom-flag-reason": "Enter a flagging reason" + "custom-flag-reason": "Wprowadź powód oznaczenia" } \ No newline at end of file diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 807f9b6bd4..5bc57dafba 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -6,7 +6,7 @@ "postcount": "Liczba postów", "email": "Adres e-mail", "confirm_email": "Potwierdź e-mail", - "account_info": "Account Info", + "account_info": "Informacje o koncie", "ban_account": "Zbanuj Konto", "ban_account_confirm": "Na pewno chcesz zbanować tego użytkownika?", "unban_account": "Odbanuj Konto", @@ -96,8 +96,8 @@ "delay_image_loading": "Opóźnienie ładowania zdjęcia", "image_load_delay_help": "Jeśli włączone, zdjęcia w temacie nie załadują się dopóki nie najedzie się", "scroll_to_my_post": "Po napisaniu odpowiedzi, wyświetl najnowsze posty", - "follow_topics_you_reply_to": "Watch topics that you reply to", - "follow_topics_you_create": "Watch topics you create", + "follow_topics_you_reply_to": "Obserwuj tematy w których uczestniczysz", + "follow_topics_you_create": "Obserwuj tematy które utworzyłeś", "grouptitle": "Tytuł grupy", "no-group-title": "Brak tytułu grupy", "select-skin": "Wybierz Skórkę", diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index eeca061a70..849a4878a1 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -20,6 +20,7 @@ "email-taken": "Email já cadastrado", "email-not-confirmed": "O seu email ainda não foi confirmado, por favor clique aqui para confirmar seu email.", "email-not-confirmed-chat": "Você não está habilitado a conversar até que seu email seja confirmado, por favor clique aqui para confirmar seu email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Este fórum exige confirmação de email, por gentileza clique aqui para digitar um email", "email-confirm-failed": "Nós não pudemos confirmar seu email, por gentileza tente novamente mais tarde.", "confirm-email-already-sent": "O email de confirmação já foi enviado, por favor aguarde %1 minuto(s) para enviar outro.", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index 0dabe24cbf..fdb92e303b 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -20,6 +20,7 @@ "email-taken": "Adresa de email este deja folostă", "email-not-confirmed": "Adresa ta de email nu a fost inca confirmata, click aici ca sa o confirmi.", "email-not-confirmed-chat": "Nu vei putea trimite mesaje daca email-ul tau nu e confirmat, click aici sa il confirmi.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Ca sa accesezi forumul trebuie sa iti confirmi email-ul, click aici ca sa intri in mail.", "email-confirm-failed": "Mail-ul tau nu a putut fi confirmat, te rog incearca mai tarziu.", "confirm-email-already-sent": "Email-ul de confirmare ti-a fost trimis, asteapta te rog %1 minut(e) ca sa trimiti inca unul.", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 80f601eb1e..a0e7ea3108 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -20,6 +20,7 @@ "email-taken": "Email занят", "email-not-confirmed": "Ваш email не подтвержден, нажмите для подтверждения.", "email-not-confirmed-chat": "Вы не можете оставлять сообщения, пока Ваш email не подтверждён. Нажмите на это сообщение чтобы получить письмо повторно.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Этот форум требует подтверждения по E-mail. Нажмите здесь для ввода E-mail.", "email-confirm-failed": "Мы не можем подтвердить Ваш E-mail, попробуйте позже.", "confirm-email-already-sent": "Сообщение для подтверждения уже выслано на E-mail. Повторная отправка возможна через %1 мин.", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index abe75583d8..07e0a50743 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -20,6 +20,7 @@ "email-taken": "Email yarafashwe mbere", "email-not-confirmed": "Email yawe ntabwo iremezwa. Kanda hano kugirango wemeze email yawe.", "email-not-confirmed-chat": "Ntabwo uremererwa kuganirira mu gikari kuko email yawe itari yemezwa. Kanda hano kugirango wemeze email yawe. ", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Uru rubuga rusaba ko wemeza ko utunze email. Kanda hano kugirango utange email yawe", "email-confirm-failed": "Ntabwo email yawe yabashije kwemezwa. Ongera ugerageze mu bundi buryo. ", "confirm-email-already-sent": "Email yo kwemeza yamaze koherezwa. Tegereza iminota (umunota) %1 mbere yo kohereza indi. ", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 3a95ab7cc6..6a614b6de4 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -20,6 +20,7 @@ "email-taken": "Email taken", "email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 8d286eaa7c..d5116a21d9 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -20,6 +20,7 @@ "email-taken": "Email je obsadený", "email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index aa23d38025..709870ec9c 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -20,6 +20,7 @@ "email-taken": "E-mail naslov je že zaseden", "email-not-confirmed": "Vaš e-mail naslov še ni bil potrjen. Prosimo kliknite tu za potrditev vašega e-mail naslova.", "email-not-confirmed-chat": "Ne morete klepetati dokler ne potrdite vašega e-mail naslova. Prosimo kliknite tu za potrditev vašega e-mail naslova.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Ta forum zahteva potrjen e-mail naslov. Prosim kliknite tu za vnos e-mail naslova.", "email-confirm-failed": "Nismo mogli potrditi vašega e-mail naslova. Prosimo poskusite ponovno.", "confirm-email-already-sent": "Potrditveni e-mail naslov je že poslan. Prosimo počakajte %1 minut(o) za ponovno pošiljanje.", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 3827af2da1..05241438be 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -20,6 +20,7 @@ "email-taken": "Адреса е-поште је заусета", "email-not-confirmed": "Ваша адреса е-поште жоуш увек није оверена, кликните овде да би сте то учинили.", "email-not-confirmed-chat": "Није вам дозвољено да ћаскате док не оверите вашу е-пошту, клкните овде да то учините.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Форум захтева потврду е-поште, кликните овде да бисте отворили е-пошту", "email-confirm-failed": "Потврда е-поште није успела, молимо вас да покушате касније.", "confirm-email-already-sent": "Конфирмациони имејл је већ послат, молимо вас да сачекате %1 минут(а) да бисте послали други.", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 9096296fbb..172f78e6cd 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -20,6 +20,7 @@ "email-taken": "Epostadress upptagen", "email-not-confirmed": "Din epostadress är ännu inte bekräftad. Klicka här för att bekräfta din epostadress.", "email-not-confirmed-chat": "Du kan ej använda chatten förrän din epostadress har blivit bekräftad, var god klicka här för att bekräfta din epostadress.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Detta forum kräver bekräftning av epostadresser, var god klicka här för att fylla i en epostadress", "email-confirm-failed": "Vi kunde ej bekräfta din epostadress, var god försök igen senare.", "confirm-email-already-sent": "Bekräftningsbrev redan skickat, var god vänta %1 minut(er) innan du skickar ett nytt.", diff --git a/public/language/th/error.json b/public/language/th/error.json index fcb210f49c..27373e3aa5 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -20,6 +20,7 @@ "email-taken": "อีเมลนี้มีการใช้แล้ว", "email-not-confirmed": "ยังไม่มีการยืนยันอีเมลของคุณ, โปรดกดยืนยันอีเมลของคุณตรงนี้", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Forum นี้ต้องการการยืนยันอีเมล กรุณากดที่นี่เพื่อระบุอีเมล", "email-confirm-failed": "เราไม่สามารถยืนยันอีเมลของคุณ ณ ขณะนี้ กรุณาลองใหม่อีกครั้งภายหลัง", "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 6fad971610..27385670ed 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -20,6 +20,7 @@ "email-taken": "E-posta Alınmış", "email-not-confirmed": "E-postanız onaylanmamış, onaylamak için lütfen buraya tıklayın.", "email-not-confirmed-chat": "E-postanız onaylanana kadar sohbet edemezsiniz, onaylamak için lütfen buraya tıklayın.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "Bu forum e-posta doğrulaması gerektirir, lütfen buraya bir e-posta adresi girin", "email-confirm-failed": "E-posta adresinizi doğrulayamıyoruz. Lütfen daha sonra tekrar deneyin.", "confirm-email-already-sent": "E-mail onayı zaten gönderilmiş, yeni bir onay göndermek için lütfen 1 dakika bekleyin.", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 38a0ab9a69..6af283b8de 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -20,6 +20,7 @@ "email-taken": "Email đã được đăng kí", "email-not-confirmed": "Email của bạn chưa được xác nhận, xin hãy nhấn vào đây để xác nhận địa chỉ này là của bạn", "email-not-confirmed-chat": "Bạn không được quyền chat nếu email của bạn chưa được xác nhận, vui lòng click vào đây để xác nhận email của bạn.", + "email-not-confirmed-email-sent": "Email của bạn chưa được xác nhận, xin hãy nhấn vào đây để xác nhận địa chỉ này là của bạn", "no-email-to-confirm": "Diễn đàn này yêu cầu xác nhận email, vui lòng nhấn vào đây để nhập email.", "email-confirm-failed": "Chúng tôi không thể xác nhận email của bạn, vui lòng thử lại sau.", "confirm-email-already-sent": "Email xác nhận đã được gửi, vui lòng chờ %1 phút để yêu cầu gửi lại.", @@ -29,8 +30,8 @@ "password-too-long": "Mật khẩu quá dài", "user-banned": "Tài khoản bị ban", "user-too-new": "Rất tiếc, bạn phải chờ %1 giây để đăng bài viết đầu tiên.", - "blacklisted-ip": "Rất tiếc, địa chỉ IP của bạn đã bị ban khỏi cộng đồng. Nếu bạn cảm thấy có gì không đúng, hãy liên lạc với người quản trị.", - "ban-expiry-missing": "Please provide an end date for this ban", + "blacklisted-ip": "Rất tiếc, địa chỉ IP của bạn đã bị cấm khỏi cộng đồng. Nếu bạn cảm thấy có gì không đúng, hãy liên lạc với người quản trị.", + "ban-expiry-missing": "Vui lòng cung cấp ngày hết hạn của lệnh cấm", "no-category": "Danh mục không tồn tại", "no-topic": "Chủ đề không tồn tại", "no-post": "Bài viết không tồn tại", @@ -47,13 +48,13 @@ "post-edit-duration-expired-hours-minutes": "Bạn chỉ được phép sửa các bài viết sau khi đăng %1 giờ(s) %2 phút(s)", "post-edit-duration-expired-days": "Bạn chỉ được phép sửa các bài viết sau khi đăng %1 ngày(s)", "post-edit-duration-expired-days-hours": "Bạn chỉ được phép sửa các bài viết sau khi đăng %1 ngày(s) %2 giờ(s)", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "post-delete-duration-expired": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giây(s)", + "post-delete-duration-expired-minutes": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 phút(s)", + "post-delete-duration-expired-minutes-seconds": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 phút(s) %2 giây(s)", + "post-delete-duration-expired-hours": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giờ(s)", + "post-delete-duration-expired-hours-minutes": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giờ(s) 2 phút(s)", + "post-delete-duration-expired-days": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s)", + "post-delete-duration-expired-days-hours": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s) %2 giờ(s)", "content-too-short": "Vui lòng nhập một bài viết dài hơn. Bài viết phải có tối thiểu %1 ký tự.", "content-too-long": "Vui lòng nhập một bài viết ngắn hơn. Bài viết chỉ có thể có tối đa %1 ký tự.", "title-too-short": "Vui lòng nhập tiêu đề dài hơn. Tiêu đề phải có tối thiểu %1 ký tự.", @@ -69,14 +70,14 @@ "guest-upload-disabled": "Khách (chưa có tài khoản) không có quyền tải lên file.", "already-favourited": "Bạn đã đánh dấu bài viết này", "already-unfavourited": "Bạn đã bỏ đánh dấu bài viết này", - "cant-ban-other-admins": "Bạn không thể ban được các admin khác", + "cant-ban-other-admins": "Bạn không thể cấm được các quản trị viên khác", "cant-remove-last-admin": "Bạn là quản trị viên duy nhất. Hãy cho thành viên khác làm quản trị viên trước khi huỷ bỏ quyền quản trị của bạn.", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "Hủy quyền quản trị của tài khoản này trước khi xóa", "invalid-image-type": "Định dạng ảnh không hợp lệ. Những định dạng được cho phép là: %1", "invalid-image-extension": "Định dạng ảnh không hợp lệ", "invalid-file-type": "Định dạng file không hợp lệ. Những định dạng được cho phép là: %1", "group-name-too-short": "Tên nhóm quá ngắn", - "group-name-too-long": "Group name too long", + "group-name-too-long": "Tên nhóm quá dài", "group-already-exists": "Nhóm đã tồn tại", "group-name-change-not-allowed": "Không cho phép đổi tên nhóm", "group-already-member": "Bạn đã là thành viên của nhóm này.", @@ -119,6 +120,6 @@ "not-in-room": "Thành viên không có trong phòng", "no-users-in-room": "Không có ai trong phòng này", "cant-kick-self": "Bạn không thể kick chính bạn ra khỏi nhóm", - "no-users-selected": "No user(s) selected", - "invalid-home-page-route": "Invalid home page route" + "no-users-selected": "Chưa có người dùng(s) nào", + "invalid-home-page-route": "Đường dẫn trang chủ không hợp lệ" } \ No newline at end of file diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 7eb329ec13..6928ecfefa 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -50,9 +50,9 @@ "topics": "Số Chủ đề", "posts": "Số bài viết", "best": "Hay nhất", - "upvoters": "Upvoters", + "upvoters": "Tán thành", "upvoted": "Tán thành", - "downvoters": "Downvoters", + "downvoters": "Phản đối", "downvoted": "Phản đối", "views": "Lượt xem", "reputation": "Điểm tín nhiệm", diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index 3035b9ac9d..8701518f30 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -26,14 +26,14 @@ "tools": "Công cụ", "flag": "Gắn cờ", "locked": "Khóa", - "pinned": "Pinned", - "moved": "Moved", + "pinned": "Đã ghim", + "moved": "Chuyển đi", "bookmark_instructions": "Bấm vào đây để quay về đọc bài viết mới nhất trong chủ đề này.", "flag_title": "Flag bài viết này để chỉnh sửa", "flag_success": "Chủ đề này đã được flag để chỉnh sửa", "deleted_message": "Chủ đề này đã bị xóa. Chỉ ban quản trị mới xem được.", "following_topic.message": "Từ giờ bạn sẽ nhận được thông báo khi có ai đó gửi bài viết trong chủ đề này", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Bạn có thể xem chủ đề này trong danh sách chủ đề chưa xem, nhưng bạn sẽ không nhận thông báo khi có ai đó đăng bài viết trong chủ đề này", "ignoring_topic.message": "Bạn sẽ không còn xem được chủ đề này trong danh sách các chủ đề chưa đọc nữa. Bạn sẽ nhận được thông báo khi bạn được đề cập tới hoặc bài viết của bạn được bỏ phiếu.", "login_to_subscribe": "Xin hãy đăng ký hoặc đăng nhập để theo dõi topic này", "markAsUnreadForAll.success": "Chủ đề đã được đánh dấu là chưa đọc toàn bộ", @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Chủ đề này sẽ được chuyển tới phần mục", "fork_topic_instruction": "Chọn vào bài gửi mà bạn muốn fork", "fork_no_pids": "Chưa chọn bài gửi nào!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 bài viết(s) đã được gửi", "fork_success": "Tạo bản sao thành công! Nhấn vào đây để chuyển tới chủ đề vừa tạo.", "delete_posts_instruction": "Chọn những bài viết bạn muốn xoá", "composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...", diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index 51ce4481b2..aa496cca6f 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -20,6 +20,7 @@ "email-taken": "此电子邮箱已被占用", "email-not-confirmed": "您的电子邮箱尚未确认,请点击这里确认您的电子邮箱。", "email-not-confirmed-chat": "您的电子邮箱尚未确认,无法聊天,请点击这里确认您的电子邮箱。", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "本论坛需要电子邮箱确认,请点击这里输入电子邮箱地址", "email-confirm-failed": "我们无法确认您的电子邮箱,请重试", "confirm-email-already-sent": "确认邮件已发出,如需重新发送请等待 %1 分钟后再试。", diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index 617902283b..3c9a9849e6 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -20,6 +20,7 @@ "email-taken": "該信箱已被使用", "email-not-confirmed": "你的電子郵件尚未確認,請點擊此處確認你的電子郵件。", "email-not-confirmed-chat": "你需要先確認電子郵件後才能進行聊天,請點擊這裡來確認你的電子郵件。", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", "no-email-to-confirm": "討論區要求電子郵件確認,請點擊這裡輸入一個電子郵件。", "email-confirm-failed": "我們無法確認你的Email,請之後再重試。", "confirm-email-already-sent": "確認電子郵件已經寄送,請等待 %1 分鐘才能再寄送另一封。", From bc961e21575bcf5ba2eb17a1a248a4ae78bea26e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Aug 2016 16:43:38 -0400 Subject: [PATCH 023/386] removing reload, closes #4936, closes #4929 --- src/controllers/admin/dashboard.js | 4 +-- src/meta.js | 46 +++------------------------ src/meta/themes.js | 2 -- src/socket.io/admin.js | 19 +++-------- src/views/admin/general/dashboard.tpl | 5 ++- src/views/admin/partials/menu.tpl | 5 --- src/views/admin/settings/advanced.tpl | 4 +-- 7 files changed, 15 insertions(+), 70 deletions(-) diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index 24a65983f1..84ee4fd3d0 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -19,8 +19,8 @@ dashboardController.get = function(req, res, next) { var notices = [ { done: !meta.reloadRequired, - doneText: 'Reload not required', - notDoneText:'Reload required' + doneText: 'Restart not required', + notDoneText:'Restart required' }, { done: plugins.hasListeners('filter:search.query'), diff --git a/src/meta.js b/src/meta.js index 1cbd1af0ae..8dfbc0d99a 100644 --- a/src/meta.js +++ b/src/meta.js @@ -41,50 +41,14 @@ var async = require('async'), }); }; + /** + * Reload deprecated as of v1.1.2+, remove in v2.x + */ Meta.reload = function(callback) { - pubsub.publish('meta:reload', {hostname: os.hostname()}); - reload(callback); + restart(); + callback(); }; - pubsub.on('meta:reload', function(data) { - if (data.hostname !== os.hostname()) { - reload(); - } - }); - - function reload(callback) { - callback = callback || function() {}; - - var plugins = require('./plugins'); - async.series([ - function (next) { - plugins.fireHook('static:app.reload', {}, next); - }, - async.apply(plugins.clearRequireCache), - async.apply(Meta.css.minify), - async.apply(Meta.js.minify, 'nodebb.min.js'), - async.apply(Meta.js.minify, 'acp.min.js'), - async.apply(Meta.sounds.init), - async.apply(languages.init), - async.apply(Meta.templates.compile), - async.apply(plugins.reload), - async.apply(plugins.reloadRoutes), - async.apply(auth.reloadRoutes), - function(next) { - Meta.config['cache-buster'] = utils.generateUUID(); - templates.flush(); - next(); - } - ], function(err) { - if (!err) { - emitter.emit('nodebb:ready'); - } - Meta.reloadRequired = false; - - callback(err); - }); - } - Meta.restart = function() { pubsub.publish('meta:restart', {hostname: os.hostname()}); restart(); diff --git a/src/meta/themes.js b/src/meta/themes.js index c3d912a222..ed6b061db9 100644 --- a/src/meta/themes.js +++ b/src/meta/themes.js @@ -161,6 +161,4 @@ module.exports = function(Meta) { nconf.set('theme_templates_path', themePath); nconf.set('theme_config', path.join(nconf.get('themes_path'), themeObj.id, 'theme.json')); }; - - }; \ No newline at end of file diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index a290238e21..556eee0290 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -49,21 +49,10 @@ SocketAdmin.before = function(socket, method, data, next) { }); }; -SocketAdmin.reload = function(socket, data, callback) { - events.log({ - type: 'reload', - uid: socket.uid, - ip: socket.ip - }); - if (process.send) { - process.send({ - action: 'reload' - }); - callback(); - } else { - meta.reload(callback); - } -}; +/** + * Reload deprecated as of v1.1.2+, remove in v2.x + */ +SocketAdmin.reload = SocketAdmin.restart; SocketAdmin.restart = function(socket, data, callback) { events.log({ diff --git a/src/views/admin/general/dashboard.tpl b/src/views/admin/general/dashboard.tpl index 6ea841352a..a478c21c16 100644 --- a/src/views/admin/general/dashboard.tpl +++ b/src/views/admin/general/dashboard.tpl @@ -95,11 +95,10 @@
    System Control
    @@ -96,7 +96,7 @@

    Lowering this value causes NodeBB to become more sensitive to spikes in load, but - may also cause the check to become too sensitive. (Reload required) + may also cause the check to become too sensitive. (Restart required)

    From 606712849358d4effa276ff9ea3d1c72b2213529 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Thu, 11 Aug 2016 17:07:15 -0400 Subject: [PATCH 024/386] Latest translations and fallbacks --- public/language/ar/modules.json | 1 + public/language/bg/modules.json | 1 + public/language/bn/modules.json | 1 + public/language/cs/modules.json | 1 + public/language/da/modules.json | 1 + public/language/de/modules.json | 1 + public/language/el/modules.json | 1 + public/language/en@pirate/modules.json | 1 + public/language/en_US/modules.json | 1 + public/language/es/category.json | 2 +- public/language/es/modules.json | 1 + public/language/et/modules.json | 1 + public/language/fa_IR/modules.json | 1 + public/language/fi/modules.json | 1 + public/language/fr/modules.json | 1 + public/language/gl/modules.json | 1 + public/language/he/modules.json | 1 + public/language/hu/modules.json | 1 + public/language/id/modules.json | 1 + public/language/it/modules.json | 1 + public/language/ja/modules.json | 1 + public/language/ko/modules.json | 1 + public/language/lt/modules.json | 1 + public/language/ms/modules.json | 1 + public/language/nb/modules.json | 1 + public/language/nl/modules.json | 1 + public/language/pl/modules.json | 1 + public/language/pt_BR/modules.json | 1 + public/language/ro/modules.json | 1 + public/language/ru/modules.json | 1 + public/language/rw/modules.json | 1 + public/language/sc/modules.json | 1 + public/language/sk/modules.json | 1 + public/language/sl/modules.json | 1 + public/language/sr/modules.json | 1 + public/language/sv/modules.json | 1 + public/language/th/modules.json | 1 + public/language/tr/modules.json | 1 + public/language/vi/modules.json | 1 + public/language/zh_CN/modules.json | 1 + public/language/zh_TW/modules.json | 1 + 41 files changed, 41 insertions(+), 1 deletion(-) diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json index c753143075..ce14cc171b 100644 --- a/public/language/ar/modules.json +++ b/public/language/ar/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "إلغاء", "bootbox.confirm": "تأكيد", diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index d87e0f446d..c9cee68f96 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Снимка", "composer.upload-picture": "Качване на изображение", "composer.upload-file": "Качване на файл", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "Добре", "bootbox.cancel": "Отказ", "bootbox.confirm": "Потвърждаване", diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json index 567a648df0..67a8aeddd3 100644 --- a/public/language/bn/modules.json +++ b/public/language/bn/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index a6d59b761f..d12f123cb1 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Obrázek", "composer.upload-picture": "Nahrát obrázek", "composer.upload-file": "Nahrát soubor", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Zrušit", "bootbox.confirm": "Potvrdit", diff --git a/public/language/da/modules.json b/public/language/da/modules.json index d54ad1ec52..01a3655865 100644 --- a/public/language/da/modules.json +++ b/public/language/da/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Annuller", "bootbox.confirm": "Bekræft", diff --git a/public/language/de/modules.json b/public/language/de/modules.json index e5f569dbed..688564237f 100644 --- a/public/language/de/modules.json +++ b/public/language/de/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Bild", "composer.upload-picture": "Bild hochladen", "composer.upload-file": "Datei hochladen", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Abbrechen", "bootbox.confirm": "Bestätigen", diff --git a/public/language/el/modules.json b/public/language/el/modules.json index af0d74e3cd..2103f5def7 100644 --- a/public/language/el/modules.json +++ b/public/language/el/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/en@pirate/modules.json b/public/language/en@pirate/modules.json index 974cdf1b43..aca98482b4 100644 --- a/public/language/en@pirate/modules.json +++ b/public/language/en@pirate/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/en_US/modules.json b/public/language/en_US/modules.json index b3775ab68f..ac0177941a 100644 --- a/public/language/en_US/modules.json +++ b/public/language/en_US/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/es/category.json b/public/language/es/category.json index 4ae8ebc1e6..1997061dea 100644 --- a/public/language/es/category.json +++ b/public/language/es/category.json @@ -2,7 +2,7 @@ "category": "Categoría", "subcategories": "Subcategorías", "new_topic_button": "Nuevo tema", - "guest-login-post": "Accede para poder escribir un mensaje", + "guest-login-post": "Accede para escribir un mensaje", "no_topics": "No hay temas en esta categoría.
    ¿Por qué no te animas y publicas uno?", "browsing": "viendo ahora", "no_replies": "Nadie ha respondido aún", diff --git a/public/language/es/modules.json b/public/language/es/modules.json index 87cc59c2fb..d47569a921 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Foto", "composer.upload-picture": "Subir foto", "composer.upload-file": "Subir archivo", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/et/modules.json b/public/language/et/modules.json index c4a2434ec1..bb0f15c66a 100644 --- a/public/language/et/modules.json +++ b/public/language/et/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Pilt", "composer.upload-picture": "Lae pilt üles", "composer.upload-file": "Lae fail üles", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "Olgu", "bootbox.cancel": "Katkesta", "bootbox.confirm": "Kinnita", diff --git a/public/language/fa_IR/modules.json b/public/language/fa_IR/modules.json index 728ffe5153..20f1a0d8d1 100644 --- a/public/language/fa_IR/modules.json +++ b/public/language/fa_IR/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "عکس", "composer.upload-picture": "بارگذاری عکس", "composer.upload-file": "بارگذاری فایل", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "باشه", "bootbox.cancel": "انصراف", "bootbox.confirm": "تایید", diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json index 77fff03fd5..bc7af01bb1 100644 --- a/public/language/fi/modules.json +++ b/public/language/fi/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index bc05644724..09746bc86b 100644 --- a/public/language/fr/modules.json +++ b/public/language/fr/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Image", "composer.upload-picture": "Envoyer une image", "composer.upload-file": "Envoyer un fichier", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Annuler", "bootbox.confirm": "Confirmer", diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index d097d97686..9bebb20db3 100644 --- a/public/language/gl/modules.json +++ b/public/language/gl/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Foto", "composer.upload-picture": "Subir foto", "composer.upload-file": "Subir arquivo", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "De acordo", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/he/modules.json b/public/language/he/modules.json index 3664bac5b3..91827e7141 100644 --- a/public/language/he/modules.json +++ b/public/language/he/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "בסדר", "bootbox.cancel": "בטל", "bootbox.confirm": "אשר", diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json index e7262c7dff..cd01a6c35f 100644 --- a/public/language/hu/modules.json +++ b/public/language/hu/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/id/modules.json b/public/language/id/modules.json index bd2946c9d7..e56070fdd4 100644 --- a/public/language/id/modules.json +++ b/public/language/id/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/it/modules.json b/public/language/it/modules.json index 1d39879d23..d18485ef7d 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Annulla", "bootbox.confirm": "Conferma", diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json index bc661721f9..0eb13821d6 100644 --- a/public/language/ja/modules.json +++ b/public/language/ja/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json index 3d7b19c6a1..1a4cfa7b1c 100644 --- a/public/language/ko/modules.json +++ b/public/language/ko/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "확인", "bootbox.cancel": "취소", "bootbox.confirm": "확인", diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json index a490086245..a339fe7558 100644 --- a/public/language/lt/modules.json +++ b/public/language/lt/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json index 774d674123..15c7f29f9f 100644 --- a/public/language/ms/modules.json +++ b/public/language/ms/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "Ok", "bootbox.cancel": "Batal", "bootbox.confirm": "Pasti", diff --git a/public/language/nb/modules.json b/public/language/nb/modules.json index 6b3309287d..999358e96c 100644 --- a/public/language/nb/modules.json +++ b/public/language/nb/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Avbryt", "bootbox.confirm": "Bekreft", diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json index 9210bfd177..5d27c1549f 100644 --- a/public/language/nl/modules.json +++ b/public/language/nl/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Afbeelding", "composer.upload-picture": "Upload afbeelding", "composer.upload-file": "Upload bestand", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Annuleren", "bootbox.confirm": "Bevestig", diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json index 1a9902604b..5461f81b39 100644 --- a/public/language/pl/modules.json +++ b/public/language/pl/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Obraz", "composer.upload-picture": "Wyślij obraz", "composer.upload-file": "Wyślij plik", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Anuluj", "bootbox.confirm": "Potwierdź", diff --git a/public/language/pt_BR/modules.json b/public/language/pt_BR/modules.json index 20d1fe1805..d9c8e1653d 100644 --- a/public/language/pt_BR/modules.json +++ b/public/language/pt_BR/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Imagem", "composer.upload-picture": "Fazer upload de Imagem", "composer.upload-file": "Fazer upload de Arquivo", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json index fa79140b83..1c25e2a5ba 100644 --- a/public/language/ro/modules.json +++ b/public/language/ro/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index a4dfd79293..17151cbc94 100644 --- a/public/language/ru/modules.json +++ b/public/language/ru/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Изображение", "composer.upload-picture": "Загрузить изображение", "composer.upload-file": "Загрузить файл", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "ОК", "bootbox.cancel": "Отмена", "bootbox.confirm": "Подтвердить", diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json index 15d1ea25eb..2ce5ceac86 100644 --- a/public/language/rw/modules.json +++ b/public/language/rw/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "Sawa", "bootbox.cancel": "Isubire", "bootbox.confirm": "Emeza", diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json index 93c5b11202..9727532086 100644 --- a/public/language/sc/modules.json +++ b/public/language/sc/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json index 0268001e7f..b7450e202c 100644 --- a/public/language/sk/modules.json +++ b/public/language/sk/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json index a9b88e3019..34b250169e 100644 --- a/public/language/sl/modules.json +++ b/public/language/sl/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "Vredu", "bootbox.cancel": "Prekliči", "bootbox.confirm": "Potrdi", diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index 24d2e521f9..2ef142c34f 100644 --- a/public/language/sr/modules.json +++ b/public/language/sr/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "ОК", "bootbox.cancel": "Откажи", "bootbox.confirm": "Потврди", diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json index 3d69a7aab8..56ddf0ceb5 100644 --- a/public/language/sv/modules.json +++ b/public/language/sv/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Bild", "composer.upload-picture": "Ladda upp bild", "composer.upload-file": "Ladda upp fil", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Avbryt", "bootbox.confirm": "Bekräfta", diff --git a/public/language/th/modules.json b/public/language/th/modules.json index 51c6065cd8..2527f30e40 100644 --- a/public/language/th/modules.json +++ b/public/language/th/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Picture", "composer.upload-picture": "Upload Image", "composer.upload-file": "Upload File", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Cancel", "bootbox.confirm": "Confirm", diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index 06e5876c68..059d8670fb 100644 --- a/public/language/tr/modules.json +++ b/public/language/tr/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Görsel", "composer.upload-picture": "Görsel Yükle", "composer.upload-file": "Dosya Yükle", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "Tamam", "bootbox.cancel": "İptal", "bootbox.confirm": "Onayla", diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json index c7863e97aa..fda573b3fd 100644 --- a/public/language/vi/modules.json +++ b/public/language/vi/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "Hình ảnh", "composer.upload-picture": "Tải ảnh lên", "composer.upload-file": "Tải file lên", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Huỷ bỏ", "bootbox.confirm": "Xác nhận", diff --git a/public/language/zh_CN/modules.json b/public/language/zh_CN/modules.json index da6d813ee3..869a8bf0c0 100644 --- a/public/language/zh_CN/modules.json +++ b/public/language/zh_CN/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "图片", "composer.upload-picture": "上传图片", "composer.upload-file": "上传文件", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "确认", "bootbox.cancel": "取消", "bootbox.confirm": "确认", diff --git a/public/language/zh_TW/modules.json b/public/language/zh_TW/modules.json index bbf71c78bf..e02c0908f1 100644 --- a/public/language/zh_TW/modules.json +++ b/public/language/zh_TW/modules.json @@ -37,6 +37,7 @@ "composer.formatting.picture": "圖片", "composer.upload-picture": "上傳圖片", "composer.upload-file": "上傳檔案", + "composer.zen_mode": "Zen Mode", "bootbox.ok": "好", "bootbox.cancel": "取消", "bootbox.confirm": "確認", From e55043e5abeab6470ed4a862e14ec8c3829b2c13 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 01:14:01 +0300 Subject: [PATCH 025/386] closes #4867 --- install/web.js | 60 ++++++++++++++++----------------- public/src/installer/install.js | 5 ++- src/views/install/index.tpl | 8 ++--- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/install/web.js b/install/web.js index 7d2cfad21a..0f4e645e8a 100644 --- a/install/web.js +++ b/install/web.js @@ -1,16 +1,16 @@ "use strict"; -var winston = require('winston'), - express = require('express'), - bodyParser = require('body-parser'), - fs = require('fs'), - path = require('path'), - less = require('less'), - async = require('async'), - uglify = require('uglify-js'), - nconf = require('nconf'), - app = express(), - server; +var winston = require('winston'); +var express = require('express'); +var bodyParser = require('body-parser'); +var fs = require('fs'); +var path = require('path'); +var less = require('less'); +var async = require('async'); +var uglify = require('uglify-js'); +var nconf = require('nconf'); +var app = express(); +var server; winston.add(winston.transports.File, { filename: 'logs/webinstall.log', @@ -22,13 +22,13 @@ winston.add(winston.transports.File, { level: 'verbose' }); -var web = {}, - scripts = [ - 'public/vendor/xregexp/xregexp.js', - 'public/vendor/xregexp/unicode/unicode-base.js', - 'public/src/utils.js', - 'public/src/installer/install.js' - ]; +var web = {}; +var scripts = [ + 'public/vendor/xregexp/xregexp.js', + 'public/vendor/xregexp/unicode/unicode-base.js', + 'public/src/utils.js', + 'public/src/installer/install.js' +]; web.install = function(port) { port = port || 4567; @@ -62,22 +62,23 @@ function setupRoutes() { } function welcome(req, res) { - var dbs = ['redis', 'mongo'], - databases = []; - - dbs.forEach(function(el) { - databases.push({ + var dbs = ['redis', 'mongo']; + var databases = dbs.map(function(el) { + return { name: el, questions: require('../src/database/' + el).questions - }); + }; }); + var defaults = require('./data/defaults'); + res.render('install/index', { databases: databases, skipDatabaseSetup: !!nconf.get('database'), error: res.locals.error ? true : false, success: res.locals.success ? true : false, - values: req.body + values: req.body, + minimumPasswordLength: defaults.minimumPasswordLength }); } @@ -104,7 +105,6 @@ function install(req, res) { } function launch(req, res) { - var pidFilePath = __dirname + '../pidfile'; res.json({}); server.close(); @@ -146,10 +146,10 @@ function compileJS(callback) { return callback(false); } - var scriptPath = path.join(__dirname, '..'), - result = uglify.minify(scripts.map(function(script) { - return path.join(scriptPath, script); - })); + var scriptPath = path.join(__dirname, '..'); + var result = uglify.minify(scripts.map(function(script) { + return path.join(scriptPath, script); + })); fs.writeFile(path.join(__dirname, '../public/nodebb.min.js'), result.code, callback); diff --git a/public/src/installer/install.js b/public/src/installer/install.js index f7224fb71a..d2e5b64fc8 100644 --- a/public/src/installer/install.js +++ b/public/src/installer/install.js @@ -44,7 +44,7 @@ $('document').ready(function() { if ($('form .admin .error').length) { ev.preventDefault(); $('html, body').animate({'scrollTop': '0px'}, 400); - + return false; } else { $('#submit .fa-spin').removeClass('hide'); @@ -69,6 +69,9 @@ $('document').ready(function() { if (!utils.isPasswordValid(field)) { parent.addClass('error'); help.html('Invalid Password.'); + } else if (field.length < $('[name="admin:password"]').attr('data-minimum-length')) { + parent.addClass('error'); + help.html('Password is too short.'); } else { parent.removeClass('error'); } diff --git a/src/views/install/index.tpl b/src/views/install/index.tpl index 75d6c1e16a..9621632a73 100644 --- a/src/views/install/index.tpl +++ b/src/views/install/index.tpl @@ -72,9 +72,9 @@
    - +
    -
    +
    @@ -119,7 +119,7 @@

    Congratulations! Your NodeBB has been set-up.

    - +

    @@ -136,7 +136,7 @@
    - +
    From bc255110cc6316a65ef610edc4fb02cd04a9717c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 01:55:38 +0300 Subject: [PATCH 026/386] closes #2832 --- public/src/admin/manage/users.js | 22 +++++++++++++++++++++- src/socket.io/admin/user.js | 18 +++++++++++++++--- src/views/admin/manage/users.tpl | 1 + 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index cc39889807..7138bb01b9 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -195,7 +195,7 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) return; } - bootbox.confirm('Warning!
    Do you really want to delete user(s)?
    This action is not reversable, all user data and content will be erased!', function(confirm) { + bootbox.confirm('Warning!
    Do you really want to delete user(s)?
    This action is not reversable, only the user account will be deleted, their posts and topics will not be deleled!', function(confirm) { if (confirm) { socket.emit('admin.user.deleteUsers', uids, function(err) { if (err) { @@ -210,6 +210,26 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) }); }); + $('.delete-user-and-content').on('click', function() { + var uids = getSelectedUids(); + if (!uids.length) { + return; + } + bootbox.confirm('Warning!
    Do you really want to delete user(s) and their content?
    This action is not reversable, all user data and content will be erased!', function(confirm) { + if (confirm) { + socket.emit('admin.user.deleteUsersAndContent', uids, function(err) { + if (err) { + return app.alertError(err.message); + } + + app.alertSuccess('User(s) Deleted!'); + removeSelected(); + unselectAll(); + }); + } + }); + }); + function handleUserCreate() { var errorEl = $('#create-modal-error'); $('#createUser').on('click', function() { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index ed7b4b378d..3fd69a8d10 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -142,7 +142,19 @@ User.sendPasswordResetEmail = function(socket, uids, callback) { }; User.deleteUsers = function(socket, uids, callback) { - if(!Array.isArray(uids)) { + deleteUsers(socket, uids, function(uid, next) { + user.deleteAccount(uid, next); + }, callback); +}; + +User.deleteUsersAndContent = function(socket, uids, callback) { + deleteUsers(socket, uids, function(uid, next) { + user.delete(socket.uid, uid, next); + }, callback); +}; + +function deleteUsers(socket, uids, method, callback) { + if (!Array.isArray(uids)) { return callback(new Error('[[error:invalid-data]]')); } @@ -156,7 +168,7 @@ User.deleteUsers = function(socket, uids, callback) { return next(new Error('[[error:cant-delete-other-admins]]')); } - user.delete(socket.uid, uid, next); + method(uid, next); }, function (next) { events.log({ @@ -169,7 +181,7 @@ User.deleteUsers = function(socket, uids, callback) { } ], next); }, callback); -}; +} User.search = function(socket, data, callback) { user.search({query: data.query, searchBy: data.searchBy, uid: socket.uid}, function(err, searchData) { diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index 6683f6e887..d6dd8c5a25 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -30,6 +30,7 @@
  • Reset Flags
  • Delete User(s)
  • +
  • Delete User(s) and Content
  • From b5bf2737e4b84f4548a4f6384fb835b5d376d87c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 02:09:11 +0300 Subject: [PATCH 027/386] bind to regular function --- src/socket.io/user/ban.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index d52251d76d..070fdf5566 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -16,7 +16,9 @@ module.exports = function(SocketUser) { } } - toggleBan(socket.uid, data.uids, banUser.bind(null, data.until || 0), function(err) { + toggleBan(socket.uid, data.uids, function(uid, next) { + banUser(data.until || 0, uid, next); + }, function(err) { if (err) { return callback(err); } From 7d4ef3907ff8503fa4931d2e164c8f44353c87b5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 02:09:52 +0300 Subject: [PATCH 028/386] fix semicolons --- src/socket.io/user/ban.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index 070fdf5566..a2c4ea50d3 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -13,7 +13,7 @@ module.exports = function(SocketUser) { data = { uids: data, until: 0 - } + }; } toggleBan(socket.uid, data.uids, function(uid, next) { @@ -71,7 +71,6 @@ module.exports = function(SocketUser) { next(); } ], callback); - }; - + } }; From c4eb1c92a8944ca2db82744ae6a1247f1ccfbc14 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 02:46:19 +0300 Subject: [PATCH 029/386] fix some tests --- tests/database/sorted.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/database/sorted.js b/tests/database/sorted.js index 5c34c380b8..dac17243ea 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -54,7 +54,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRange('sortedSetTest1', 0, 0, function(err, value) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.equal(value, 'value1'); + assert.deepStrictEqual(value, ['value1']); done(); }); }); @@ -63,7 +63,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRange('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, ['value1', 'value2', 'value3']); + assert.deepStrictEqual(values, ['value1', 'value2', 'value3']); done(); }); }); @@ -74,7 +74,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRange('sortedSetTest1', 0, 0, function(err, value) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.equal(value, 'value3'); + assert.deepStrictEqual(value, ['value3']); done(); }); }); @@ -83,7 +83,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRange('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, ['value3', 'value2', 'value1']); + assert.deepStrictEqual(values, ['value3', 'value2', 'value1']); done(); }); }); @@ -94,7 +94,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRangeWithScores('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}, {value: 'value3', score: 3}]); + assert.deepStrictEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}, {value: 'value3', score: 3}]); done(); }); }); @@ -105,7 +105,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRangeWithScores('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}, {value: 'value1', score: 1}]); + assert.deepStrictEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}, {value: 'value1', score: 1}]); done(); }); }); @@ -116,7 +116,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRangeByScore('sortedSetTest1', 0, -1, '-inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, ['value1', 'value2']); + assert.deepStrictEqual(values, ['value1', 'value2']); done(); }); }); @@ -127,7 +127,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRangeByScore('sortedSetTest1', 0, -1, '+inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, ['value3', 'value2']); + assert.deepStrictEqual(values, ['value3', 'value2']); done(); }); }); @@ -138,7 +138,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRangeByScoreWithScores('sortedSetTest1', 0, -1, '-inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}]); + assert.deepStrictEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}]); done(); }); }); @@ -149,7 +149,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRangeByScoreWithScores('sortedSetTest1', 0, -1, '+inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}]); + assert.deepStrictEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}]); done(); }); }); From 63f5cd0c79f60bacaf94ec9b6d93825e534f8aa9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 02:59:19 +0300 Subject: [PATCH 030/386] removed deepStrictEqual --- tests/database/sorted.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/database/sorted.js b/tests/database/sorted.js index dac17243ea..9705f4fff1 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -54,7 +54,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRange('sortedSetTest1', 0, 0, function(err, value) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(value, ['value1']); + assert.deepEqual(value, ['value1']); done(); }); }); @@ -63,7 +63,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRange('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, ['value1', 'value2', 'value3']); + assert.deepEqual(values, ['value1', 'value2', 'value3']); done(); }); }); @@ -74,7 +74,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRange('sortedSetTest1', 0, 0, function(err, value) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(value, ['value3']); + assert.deepEqual(value, ['value3']); done(); }); }); @@ -83,7 +83,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRange('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, ['value3', 'value2', 'value1']); + assert.deepEqual(values, ['value3', 'value2', 'value1']); done(); }); }); @@ -94,7 +94,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRangeWithScores('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}, {value: 'value3', score: 3}]); + assert.deepEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}, {value: 'value3', score: 3}]); done(); }); }); @@ -105,7 +105,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRangeWithScores('sortedSetTest1', 0, -1, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}, {value: 'value1', score: 1}]); + assert.deepEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}, {value: 'value1', score: 1}]); done(); }); }); @@ -116,7 +116,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRangeByScore('sortedSetTest1', 0, -1, '-inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, ['value1', 'value2']); + assert.deepEqual(values, ['value1', 'value2']); done(); }); }); @@ -127,7 +127,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRangeByScore('sortedSetTest1', 0, -1, '+inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, ['value3', 'value2']); + assert.deepEqual(values, ['value3', 'value2']); done(); }); }); @@ -138,7 +138,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRangeByScoreWithScores('sortedSetTest1', 0, -1, '-inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}]); + assert.deepEqual(values, [{value: 'value1', score: 1}, {value: 'value2', score: 2}]); done(); }); }); @@ -149,7 +149,7 @@ describe('Sorted Set methods', function() { db.getSortedSetRevRangeByScoreWithScores('sortedSetTest1', 0, -1, '+inf', 2, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); - assert.deepStrictEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}]); + assert.deepEqual(values, [{value: 'value3', score: 3}, {value: 'value2', score: 2}]); done(); }); }); From 0efe315790deed2632e1a27babbac0189cd8a20f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 12:57:23 +0300 Subject: [PATCH 031/386] closes #4631 --- public/src/client/topic/events.js | 2 +- public/src/client/topic/posts.js | 2 +- public/src/client/topic/threadTools.js | 26 ++++++++++++++++++++++---- src/privileges/topics.js | 15 +++++++++------ src/topics/create.js | 4 ++++ src/topics/posts.js | 2 +- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index 0c6ee5ea06..68a864198e 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -188,7 +188,7 @@ define('forum/topic/events', [ var isDeleted = postEl.hasClass('deleted'); postTools.toggle(data.pid, isDeleted); - if (!app.user.isAdmin && !app.user.isGlobalMod && parseInt(data.uid, 10) !== parseInt(app.user.uid, 10)) { + if (!ajaxify.data.privileges.isAdminOrMod && parseInt(data.uid, 10) !== parseInt(app.user.uid, 10)) { postEl.find('[component="post/tools"]').toggleClass('hidden', isDeleted); if (isDeleted) { postEl.find('[component="post/content"]').translateHtml('[[topic:post_is_deleted]]'); diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index b31b5fc0ed..656364b3a1 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -31,7 +31,7 @@ define('forum/topic/posts', [ post.display_delete_tools = (ajaxify.data.privileges['posts:delete'] && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = ajaxify.data.privileges.isAdminOrMod; - post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); + post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || (post.selfPost && !ajaxify.data.locked) || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); }); updatePostCounts(data.posts); diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index 3110b92a4d..a18c069cd5 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -173,15 +173,24 @@ define('forum/topic/threadTools', [ return; } - var isLocked = data.isLocked && !app.user.isAdmin; + var isLocked = data.isLocked && !ajaxify.data.privileges.isAdminOrMod; components.get('topic/lock').toggleClass('hidden', data.isLocked); components.get('topic/unlock').toggleClass('hidden', !data.isLocked); - components.get('topic/reply/container').toggleClass('hidden', isLocked); - components.get('topic/reply/locked').toggleClass('hidden', !isLocked); - threadEl.find('[component="post/reply"], [component="post/quote"], [component="post/edit"], [component="post/delete"]').toggleClass('hidden', isLocked); + var hideReply = (data.isLocked || ajaxify.data.deleted) && !ajaxify.data.privileges.isAdminOrMod; + + components.get('topic/reply/container').toggleClass('hidden', hideReply); + components.get('topic/reply/locked').toggleClass('hidden', ajaxify.data.privileges.isAdminOrMod || !data.isLocked || ajaxify.data.deleted); + + threadEl.find('[component="post"]:not(.deleted) [component="post/reply"], [component="post"]:not(.deleted) [component="post/quote"]').toggleClass('hidden', hideReply); + threadEl.find('[component="post/edit"], [component="post/delete"]').toggleClass('hidden', isLocked); + + threadEl.find('[component="post"][data-uid="'+app.user.uid+'"].deleted [component="post/tools"]').toggleClass('hidden', isLocked); + $('[component="post/header"] i.fa-lock').toggleClass('hidden', !data.isLocked); + $('[component="post/tools"] .dropdown-menu').html(''); + ajaxify.data.locked = data.isLocked; }; ThreadTools.setDeleteState = function(data) { @@ -195,9 +204,17 @@ define('forum/topic/threadTools', [ components.get('topic/purge').toggleClass('hidden', !data.isDelete); components.get('topic/deleted/message').toggleClass('hidden', !data.isDelete); + var hideReply = data.isDelete && !ajaxify.data.privileges.isAdminOrMod; + + components.get('topic/reply/container').toggleClass('hidden', hideReply); + components.get('topic/reply/locked').toggleClass('hidden', ajaxify.data.privileges.isAdminOrMod || !ajaxify.data.locked || data.isDelete); + threadEl.find('[component="post"]:not(.deleted) [component="post/reply"], [component="post"]:not(.deleted) [component="post/quote"]').toggleClass('hidden', hideReply); + threadEl.toggleClass('deleted', data.isDelete); + ajaxify.data.deleted = data.isDelete; }; + ThreadTools.setPinnedState = function(data) { var threadEl = components.get('topic'); if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) { @@ -207,6 +224,7 @@ define('forum/topic/threadTools', [ components.get('topic/pin').toggleClass('hidden', data.isPinned); components.get('topic/unpin').toggleClass('hidden', !data.isPinned); $('[component="post/header"] i.fa-thumb-tack').toggleClass('hidden', !data.isPinned); + ajaxify.data.pinned = data.isPinned; }; function setFollowState(state) { diff --git a/src/privileges/topics.js b/src/privileges/topics.js index c9f0ec717a..b6fffb8a6a 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -17,7 +17,7 @@ module.exports = function(privileges) { privileges.topics.get = function(tid, uid, callback) { var topic; async.waterfall([ - async.apply(topics.getTopicFields, tid, ['cid', 'uid', 'locked']), + async.apply(topics.getTopicFields, tid, ['cid', 'uid', 'locked', 'deleted']), function(_topic, next) { topic = _topic; async.parallel({ @@ -42,14 +42,19 @@ module.exports = function(privileges) { var disabled = parseInt(results.disabled, 10) === 1; var locked = parseInt(topic.locked, 10) === 1; + var deleted = parseInt(topic.deleted, 10) === 1; + var isAdminOrMod = results.isAdministrator || results.isModerator; var editable = isAdminOrMod; var deletable = isAdminOrMod || (results.isOwner && results['topics:delete'][0]); plugins.fireHook('filter:privileges.topics.get', { - 'topics:reply': (results['topics:reply'][0] && !locked) || isAdminOrMod, - read: results.read[0] || isAdminOrMod, + 'topics:reply': (results['topics:reply'][0] && !locked && !deleted) || isAdminOrMod, 'topics:read': results['topics:read'][0] || isAdminOrMod, + 'topics:delete': (results.isOwner && results['topics:delete'][0]) || isAdminOrMod, + 'posts:edit': (results['posts:edit'][0] && !locked) || isAdminOrMod, + 'posts:delete': (results['posts:delete'][0] && !locked) || isAdminOrMod, + read: results.read[0] || isAdminOrMod, view_thread_tools: editable || deletable, editable: editable, deletable: deletable, @@ -57,9 +62,7 @@ module.exports = function(privileges) { isAdminOrMod: isAdminOrMod, disabled: disabled, tid: tid, - uid: uid, - 'posts:edit': (results['posts:edit'][0] && !locked) || isAdminOrMod, - 'posts:delete': (results['posts:delete'][0] && !locked) || isAdminOrMod + uid: uid }, callback); }); }; diff --git a/src/topics/create.js b/src/topics/create.js index c79e0758f4..cd1bf23a62 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -205,6 +205,10 @@ module.exports = function(Topics) { return next(new Error('[[error:topic-locked]]')); } + if (parseInt(results.topicData.deleted, 10) === 1 && !results.isAdminOrMod) { + return next(new Error('[[error:topic-deleted]]')); + } + if (!results.canReply) { return next(new Error('[[error:no-privileges]]')); } diff --git a/src/topics/posts.js b/src/topics/posts.js index 45e46cd3c8..80bde62334 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -145,7 +145,7 @@ module.exports = function(Topics) { post.display_delete_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:delete']); post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0; - post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || ((loggedIn || topicData.postSharing.length) && !post.deleted); + post.display_post_menu = topicPrivileges.isAdminOrMod || (post.selfPost && !topicData.locked) || ((loggedIn || topicData.postSharing.length) && !post.deleted); post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined; if (post.deleted && !(topicPrivileges.isAdminOrMod || post.selfPost)) { From 4ca53703e33018705cc759280a80ee2913579e1e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 13:41:49 +0300 Subject: [PATCH 032/386] added dupe group create test --- tests/groups.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/groups.js b/tests/groups.js index 81fb253433..a86495071f 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -162,6 +162,14 @@ describe('Groups', function() { Groups.get('foo', {}, done); }); }); + + it('should fail to create group with duplicate group name', function(done) { + Groups.create({name: 'foo'}, function(err) { + assert(err); + assert.equal(err.message, '[[error:group-already-exists]]'); + done(); + }); + }); }); describe('.hide()', function() { From a13bc64f402fe44c758d082b6298fec8e5ee5839 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 13:49:41 +0300 Subject: [PATCH 033/386] wait for db flush to complete to carry on with other test suites fixes the problem where tests would sometimes fail due to timing issues --- tests/categories.js | 4 ++-- tests/database/hash.js | 4 ++-- tests/database/keys.js | 4 ++-- tests/database/list.js | 4 ++-- tests/database/sets.js | 4 ++-- tests/database/sorted.js | 4 ++-- tests/groups.js | 4 ++-- tests/messaging.js | 4 ++-- tests/topics.js | 4 ++-- tests/user.js | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/categories.js b/tests/categories.js index 7115b88597..4ec06ab3b4 100644 --- a/tests/categories.js +++ b/tests/categories.js @@ -89,7 +89,7 @@ describe('Categories', function() { }); }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/database/hash.js b/tests/database/hash.js index 469172ce70..a178280e3c 100644 --- a/tests/database/hash.js +++ b/tests/database/hash.js @@ -369,7 +369,7 @@ describe('Hash methods', function() { - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/database/keys.js b/tests/database/keys.js index 36e5fe5989..832c91bcba 100644 --- a/tests/database/keys.js +++ b/tests/database/keys.js @@ -142,7 +142,7 @@ describe('Key methods', function() { }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/database/list.js b/tests/database/list.js index 59fa82aa58..137c465c2e 100644 --- a/tests/database/list.js +++ b/tests/database/list.js @@ -158,7 +158,7 @@ describe('List methods', function() { }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/database/sets.js b/tests/database/sets.js index a7a23f4dd8..2b852a5341 100644 --- a/tests/database/sets.js +++ b/tests/database/sets.js @@ -229,7 +229,7 @@ describe('Set methods', function() { }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/database/sorted.js b/tests/database/sorted.js index 9705f4fff1..64e8891a30 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -533,7 +533,7 @@ describe('Sorted Set methods', function() { }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/groups.js b/tests/groups.js index a86495071f..6c30bddd77 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -342,7 +342,7 @@ describe('Groups', function() { }); }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/messaging.js b/tests/messaging.js index 4758fde40b..3092e42b35 100644 --- a/tests/messaging.js +++ b/tests/messaging.js @@ -66,7 +66,7 @@ describe('Messaging Library', function() { }); }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/topics.js b/tests/topics.js index 0c04d875bf..dcf7f1067e 100644 --- a/tests/topics.js +++ b/tests/topics.js @@ -387,7 +387,7 @@ describe('Topic\'s', function() { }); }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); diff --git a/tests/user.js b/tests/user.js index 570b002808..30a835fe83 100644 --- a/tests/user.js +++ b/tests/user.js @@ -282,7 +282,7 @@ describe('User', function() { }); }); - after(function() { - db.flushdb(); + after(function(done) { + db.flushdb(done); }); }); \ No newline at end of file From 650f4d6ed535daebd6e31a9fe5c4209351df9bbf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 14:11:40 +0300 Subject: [PATCH 034/386] moved sitemap to its own file --- src/controllers/index.js | 63 ++--------------------------------- src/controllers/sitemap.js | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 src/controllers/sitemap.js diff --git a/src/controllers/index.js b/src/controllers/index.js index bd6daa6581..4ad3d5ab0a 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -8,7 +8,6 @@ var winston = require('winston'); var meta = require('../meta'); var user = require('../user'); var plugins = require('../plugins'); -var sitemap = require('../sitemap'); var helpers = require('./helpers'); var Controllers = { @@ -27,7 +26,8 @@ var Controllers = { authentication: require('./authentication'), api: require('./api'), admin: require('./admin'), - globalMods: require('./globalmods') + globalMods: require('./globalmods'), + sitemap: require('./sitemap') }; @@ -251,65 +251,6 @@ Controllers.confirmEmail = function(req, res) { }); }; -Controllers.sitemap = {}; -Controllers.sitemap.render = function(req, res, next) { - sitemap.render(function(err, tplData) { - if (err) { - return next(err); - } - - Controllers.render('sitemap', tplData, function(err, xml) { - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); - }); -}; - -Controllers.sitemap.getPages = function(req, res, next) { - if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { - return next(); - } - - sitemap.getPages(function(err, xml) { - if (err) { - return next(err); - } - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); -}; - -Controllers.sitemap.getCategories = function(req, res, next) { - if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { - return next(); - } - - sitemap.getCategories(function(err, xml) { - if (err) { - return next(err); - } - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); -}; - -Controllers.sitemap.getTopicPage = function(req, res, next) { - if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { - return next(); - } - - sitemap.getTopicPage(parseInt(req.params[0], 10), function(err, xml) { - if (err) { - return next(err); - } else if (!xml) { - return next(); - } - - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); -}; - Controllers.robots = function (req, res) { res.set('Content-Type', 'text/plain'); diff --git a/src/controllers/sitemap.js b/src/controllers/sitemap.js new file mode 100644 index 0000000000..eee344fc92 --- /dev/null +++ b/src/controllers/sitemap.js @@ -0,0 +1,68 @@ +'use strict'; + +var sitemap = require('../sitemap'); +var meta = require('../meta'); + +var sitemapController = {}; +sitemapController.render = function(req, res, next) { + sitemap.render(function(err, tplData) { + if (err) { + return next(err); + } + var Controllers = require('./index'); + Controllers.render('sitemap', tplData, function(err, xml) { + if (err) { + return next(err); + } + res.header('Content-Type', 'application/xml'); + res.send(xml); + }); + }); +}; + +sitemapController.getPages = function(req, res, next) { + if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { + return next(); + } + + sitemap.getPages(function(err, xml) { + if (err) { + return next(err); + } + res.header('Content-Type', 'application/xml'); + res.send(xml); + }); +}; + +sitemapController.getCategories = function(req, res, next) { + if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { + return next(); + } + + sitemap.getCategories(function(err, xml) { + if (err) { + return next(err); + } + res.header('Content-Type', 'application/xml'); + res.send(xml); + }); +}; + +sitemapController.getTopicPage = function(req, res, next) { + if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { + return next(); + } + + sitemap.getTopicPage(parseInt(req.params[0], 10), function(err, xml) { + if (err) { + return next(err); + } else if (!xml) { + return next(); + } + + res.header('Content-Type', 'application/xml'); + res.send(xml); + }); +}; + +module.exports = sitemapController; \ No newline at end of file From b9ad7f2dbb896d9d040ad48db34abbe4ec715821 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 15:06:03 +0300 Subject: [PATCH 035/386] remove unused group code from settings --- src/controllers/accounts/settings.js | 10 ---------- src/user/settings.js | 1 - 2 files changed, 11 deletions(-) diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index 483a7e5eb1..9efa8ebe6b 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -32,9 +32,6 @@ settingsController.get = function(req, res, callback) { settings: function(next) { user.getSettings(userData.uid, next); }, - userGroups: function(next) { - groups.getUserGroupsFromSet('groups:createtime', [userData.uid], next); - }, languages: function(next) { languages.list(next); }, @@ -49,9 +46,6 @@ settingsController.get = function(req, res, callback) { }, function(results, next) { userData.settings = results.settings; - userData.userGroups = results.userGroups[0].filter(function(group) { - return group && group.userTitleEnabled && !groups.isPrivilegeGroup(group.name) && group.name !== 'registered-users'; - }); userData.languages = results.languages; userData.homePageRoutes = results.homePageRoutes; userData.ips = results.ips; @@ -118,10 +112,6 @@ settingsController.get = function(req, res, callback) { skin.selected = skin.value === userData.settings.bootswatchSkin; }); - userData.userGroups.forEach(function(group) { - group.selected = group.name === userData.settings.groupTitle; - }); - userData.languages.forEach(function(language) { language.selected = language.code === userData.settings.userLang; }); diff --git a/src/user/settings.js b/src/user/settings.js index 7b78ea3c72..55bf2b14ff 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -122,7 +122,6 @@ module.exports = function(User) { restrictChat: data.restrictChat, topicSearchEnabled: data.topicSearchEnabled, delayImageLoading: data.delayImageLoading, - groupTitle: data.groupTitle, homePageRoute : ((data.homePageRoute === 'custom' ? data.homePageCustom : data.homePageRoute) || '').replace(/^\//, ''), scrollToMyPost: data.scrollToMyPost }; From 32320018ab875ca79442c25193a6cf36fd6aeb3f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 15:29:41 +0300 Subject: [PATCH 036/386] closes #4911 --- public/src/app.js | 20 +++++++++++--------- public/src/client/topic/fork.js | 18 +++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 390bde2f04..a913517363 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -511,20 +511,22 @@ app.cacheBuster = null; app.parseAndTranslate = function(template, blockName, data, callback) { require(['translator'], function(translator) { + function translate(html, callback) { + translator.translate(html, function(translatedHTML) { + translatedHTML = translator.unescape(translatedHTML); + callback($(translatedHTML)); + }); + } + if (typeof blockName === 'string') { templates.parse(template, blockName, data, function(html) { - translator.translate(html, function(translatedHTML) { - translatedHTML = translator.unescape(translatedHTML); - callback($(translatedHTML)); - }); + translate(html, callback); }); } else { - callback = data, data = blockName; + callback = data; + data = blockName; templates.parse(template, data, function(html) { - translator.translate(html, function(translatedHTML) { - translatedHTML = translator.unescape(translatedHTML); - callback($(translatedHTML)); - }); + translate(html, callback); }); } }); diff --git a/public/src/client/topic/fork.js b/public/src/client/topic/fork.js index feb02a9f78..2083a4867e 100644 --- a/public/src/client/topic/fork.js +++ b/public/src/client/topic/fork.js @@ -1,6 +1,6 @@ 'use strict'; -/* globals define, app, ajaxify, socket, templates, translator */ +/* globals define, app, ajaxify, socket */ define('forum/topic/fork', ['components', 'postSelect'], function(components, postSelect) { @@ -10,11 +10,17 @@ define('forum/topic/fork', ['components', 'postSelect'], function(components, po Fork.init = function() { $('.topic').on('click', '[component="topic/fork"]', onForkThreadClicked); + $(window).on('action:ajaxify.start', onAjaxifyStart); }; + function onAjaxifyStart() { + closeForkModal(); + $(window).off('action:ajaxify.start', onAjaxifyStart); + } + function onForkThreadClicked() { - parseModal(function(html) { - forkModal = $(html); + app.parseAndTranslate('partials/fork_thread_modal', {}, function(html) { + forkModal = html; forkCommit = forkModal.find('#fork_thread_commit'); @@ -33,12 +39,6 @@ define('forum/topic/fork', ['components', 'postSelect'], function(components, po }); } - function parseModal(callback) { - templates.parse('partials/fork_thread_modal', {}, function(html) { - translator.translate(html, callback); - }); - } - function createTopicFromPosts() { forkCommit.attr('disabled', true); socket.emit('topics.createTopicFromPosts', { From ad4910d44d87c678b66a82ab8fdf6af18a0de3b3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 15:47:11 +0300 Subject: [PATCH 037/386] up async to 2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9ad9c178f..2ee7dd6323 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- ./tests -t 10000" }, "dependencies": { - "async": "~1.5.0", + "async": "~2.0.1", "autoprefixer": "^6.2.3", "bcryptjs": "~2.3.0", "body-parser": "^1.9.0", From ecb4e3ad7c2c62442d2028c7b2e74bbdf831ee84 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 16:01:01 +0300 Subject: [PATCH 038/386] up deps --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2ee7dd6323..8d7b5cad95 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "compression": "^1.1.0", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", - "connect-mongo": "~1.1.0", + "connect-mongo": "~1.3.2", "connect-multiparty": "^2.0.0", - "connect-redis": "~3.0.2", + "connect-redis": "~3.1.0", "cookie-parser": "^1.3.3", "cron": "^1.0.5", "csurf": "^1.6.1", @@ -43,7 +43,7 @@ "mime": "^1.3.4", "minimist": "^1.1.1", "mkdirp": "~0.5.0", - "mongodb": "~2.1.3", + "mongodb": "~2.2.5", "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", @@ -67,7 +67,7 @@ "passport-local": "1.0.0", "postcss": "^5.0.13", "prompt": "^1.0.0", - "redis": "~2.4.2", + "redis": "~2.6.2", "request": "^2.44.0", "rimraf": "~2.5.0", "rss": "^1.0.0", @@ -80,7 +80,7 @@ "socketio-wildcard": "~0.3.0", "string": "^3.0.0", "templates.js": "0.3.4", - "toobusy-js": "^0.4.2", + "toobusy-js": "^0.5.1", "uglify-js": "^2.6.0", "underscore": "^1.8.3", "underscore.deep": "^0.5.1", From a5c409c6f7e30f81bc0c4531aea62170e69d911a Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Fri, 12 Aug 2016 09:02:39 -0400 Subject: [PATCH 039/386] Latest translations and fallbacks --- public/language/bg/modules.json | 2 +- public/language/fr/modules.json | 2 +- public/language/pt_BR/error.json | 2 +- public/language/pt_BR/modules.json | 2 +- public/language/pt_BR/topic.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json index c9cee68f96..e17f98847b 100644 --- a/public/language/bg/modules.json +++ b/public/language/bg/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Снимка", "composer.upload-picture": "Качване на изображение", "composer.upload-file": "Качване на файл", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Режим Дзен", "bootbox.ok": "Добре", "bootbox.cancel": "Отказ", "bootbox.confirm": "Потвърждаване", diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json index 09746bc86b..9a9eaa0675 100644 --- a/public/language/fr/modules.json +++ b/public/language/fr/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Image", "composer.upload-picture": "Envoyer une image", "composer.upload-file": "Envoyer un fichier", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Mode Zen", "bootbox.ok": "OK", "bootbox.cancel": "Annuler", "bootbox.confirm": "Confirmer", diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index 849a4878a1..64286e8352 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -20,7 +20,7 @@ "email-taken": "Email já cadastrado", "email-not-confirmed": "O seu email ainda não foi confirmado, por favor clique aqui para confirmar seu email.", "email-not-confirmed-chat": "Você não está habilitado a conversar até que seu email seja confirmado, por favor clique aqui para confirmar seu email.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "O seu email ainda não foi confirmado, por favor confira a sua caixa de entrada pelo email de confirmação.", "no-email-to-confirm": "Este fórum exige confirmação de email, por gentileza clique aqui para digitar um email", "email-confirm-failed": "Nós não pudemos confirmar seu email, por gentileza tente novamente mais tarde.", "confirm-email-already-sent": "O email de confirmação já foi enviado, por favor aguarde %1 minuto(s) para enviar outro.", diff --git a/public/language/pt_BR/modules.json b/public/language/pt_BR/modules.json index d9c8e1653d..d02547efe4 100644 --- a/public/language/pt_BR/modules.json +++ b/public/language/pt_BR/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Imagem", "composer.upload-picture": "Fazer upload de Imagem", "composer.upload-file": "Fazer upload de Arquivo", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Modo Zen", "bootbox.ok": "OK", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json index dec0d42e8e..194f00c316 100644 --- a/public/language/pt_BR/topic.json +++ b/public/language/pt_BR/topic.json @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Este tópico será movido para a categoria", "fork_topic_instruction": "Clique nos posts que você quer ramificar", "fork_no_pids": "Nenhum post selecionado!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 post(s) selecionado(s)", "fork_success": "Tópico ramificado com sucesso! Clique aqui para ir ao tópico ramificado.", "delete_posts_instruction": "Clique nos posts que você deseja deletar/limpar", "composer.title_placeholder": "Digite aqui o título para o seu tópico...", From 80c096dedb366570d2eae0d5f666f35d4fcc8c10 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 12 Aug 2016 12:52:44 -0400 Subject: [PATCH 040/386] Revert "up async to 2.0.1" This reverts commit ad4910d44d87c678b66a82ab8fdf6af18a0de3b3. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d7b5cad95..ef16dbbad1 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- ./tests -t 10000" }, "dependencies": { - "async": "~2.0.1", + "async": "~1.5.0", "autoprefixer": "^6.2.3", "bcryptjs": "~2.3.0", "body-parser": "^1.9.0", From 43184935bd9084dbb67e5787ba0e093f9bad563c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 12 Aug 2016 20:47:43 +0300 Subject: [PATCH 041/386] revert mongodb packages --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ef16dbbad1..4d457b471a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "compression": "^1.1.0", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", - "connect-mongo": "~1.3.2", + "connect-mongo": "~1.1.0", "connect-multiparty": "^2.0.0", "connect-redis": "~3.1.0", "cookie-parser": "^1.3.3", @@ -43,7 +43,7 @@ "mime": "^1.3.4", "minimist": "^1.1.1", "mkdirp": "~0.5.0", - "mongodb": "~2.2.5", + "mongodb": "~2.1.3", "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", From 28331389a6fc26e95d04841591f5db1ad74d973d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 13 Aug 2016 01:20:19 +0300 Subject: [PATCH 042/386] added getTopicWithPosts test --- tests/topics.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/topics.js b/tests/topics.js index dcf7f1067e..7103b32689 100644 --- a/tests/topics.js +++ b/tests/topics.js @@ -132,7 +132,7 @@ describe('Topic\'s', function() { var newTopic; var newPost; - beforeEach(function(done) { + before(function(done) { topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { newTopic = result.topicData; newPost = result.postData; @@ -145,6 +145,28 @@ describe('Topic\'s', function() { topics.getTopicData(newTopic.tid, done); }); }); + + describe('.getTopicWithPosts', function() { + it('should get a topic with posts and other data', function(done) { + topics.getTopicData(newTopic.tid, function(err, topicData) { + if (err) { + return done(err); + } + topics.getTopicWithPosts(topicData, 'tid:' + newTopic.tid + ':posts', topic.userId, 0, -1, false, function(err, data) { + if (err) { + return done(err); + } + assert(data); + assert.equal(data.category.cid, topic.categoryId); + assert.equal(data.unreplied, true); + assert.equal(data.deleted, false); + assert.equal(data.locked, false); + assert.equal(data.pinned, false); + done(); + }); + }); + }); + }); }); describe('Title escaping', function() { From 85dfe7704fbb1496b3cf067d88ec7a8da80e81aa Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 13 Aug 2016 01:20:27 +0300 Subject: [PATCH 043/386] moved related up --- src/topics.js | 8 +++----- src/topics/tags.js | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/topics.js b/src/topics.js index b92d0b51bf..0079c501ce 100644 --- a/src/topics.js +++ b/src/topics.js @@ -192,7 +192,8 @@ var social = require('./social'); isFollowing: async.apply(Topics.isFollowing, [topicData.tid], uid), isIgnoring: async.apply(Topics.isIgnoring, [topicData.tid], uid), bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid), - postSharing: async.apply(social.getActivePostSharing) + postSharing: async.apply(social.getActivePostSharing), + related: async.apply(Topics.getRelatedTopics, topicData, uid) }, next); }, function (results, next) { @@ -205,6 +206,7 @@ var social = require('./social'); topicData.isIgnoring = results.isIgnoring[0]; topicData.bookmark = results.bookmark; topicData.postSharing = results.postSharing; + topicData.related = results.related || []; topicData.unreplied = parseInt(topicData.postcount, 10) === 1; topicData.deleted = parseInt(topicData.deleted, 10) === 1; @@ -213,10 +215,6 @@ var social = require('./social'); topicData.icons = []; - Topics.getRelatedTopics(topicData, uid, next); - }, - function (related, next) { - topicData.related = related || []; plugins.fireHook('filter:topic.get', {topic: topicData, uid: uid}, next); }, function (data, next) { diff --git a/src/topics/tags.js b/src/topics/tags.js index 83702331b7..ec16c09ff6 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -316,7 +316,7 @@ module.exports = function(Topics) { return plugins.fireHook('filter:topic.getRelatedTopics', {topic: topicData, uid: uid}, callback); } - var maximumTopics = parseInt(meta.config.maximumRelatedTopics, 10); + var maximumTopics = parseInt(meta.config.maximumRelatedTopics, 10) || 0; if (maximumTopics === 0 || !topicData.tags.length) { return callback(null, []); } From de007772a489fd47fffa7f29e3263ba4855f3c9b Mon Sep 17 00:00:00 2001 From: Accalia de Elementia Date: Sat, 13 Aug 2016 12:50:06 +0000 Subject: [PATCH 044/386] feat: Allow listing all groups via websocket also add missing radix to parsing of `after` in groups.loadMore websocket method. --- src/socket.io/groups.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 7d9ce46a70..5df97f80f5 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -217,12 +217,12 @@ SocketGroups.search = function(socket, data, callback) { }; SocketGroups.loadMore = function(socket, data, callback) { - if (!data.sort || !data.after) { + if (!data.sort || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { return callback(); } var groupsPerPage = 9; - var start = parseInt(data.after); + var start = parseInt(data.after, 10); var stop = start + groupsPerPage - 1; groupsController.getGroupsFromSet(socket.uid, data.sort, start, stop, callback); }; From fd71b1afb6f323ac920da0a62fca4ba69069f654 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Sat, 13 Aug 2016 09:02:25 -0400 Subject: [PATCH 045/386] Latest translations and fallbacks --- public/language/de/category.json | 2 +- public/language/de/register.json | 8 ++++---- public/language/fa_IR/error.json | 2 +- public/language/fa_IR/modules.json | 2 +- public/language/fa_IR/topic.json | 2 +- public/language/tr/error.json | 2 +- public/language/tr/modules.json | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/public/language/de/category.json b/public/language/de/category.json index 6ce06deed2..9169f01756 100644 --- a/public/language/de/category.json +++ b/public/language/de/category.json @@ -2,7 +2,7 @@ "category": "Kategorie", "subcategories": "Unterkategorien", "new_topic_button": "Neues Thema", - "guest-login-post": "Anmelden, um einen Beitrag zu erstellen", + "guest-login-post": "Melde dich an, um einen Beitrag zu erstellen", "no_topics": "Es gibt noch keine Themen in dieser Kategorie.
    Warum beginnst du nicht eins?", "browsing": "Aktiv", "no_replies": "Niemand hat geantwortet", diff --git a/public/language/de/register.json b/public/language/de/register.json index 41998933f1..9d1b7ca049 100644 --- a/public/language/de/register.json +++ b/public/language/de/register.json @@ -1,6 +1,6 @@ { "register": "Registrieren", - "cancel_registration": "Cancel Registration", + "cancel_registration": "Registrierungsvorgang abbrechen", "help.email": "Deine E-Mail Adresse ist standardmäßig nicht öffentlich sichtbar.", "help.username_restrictions": "Einen einmaligen Benutzernamen. %1-%2 Zeichen. Andere Benutzer können dich mit @Benutzername anschreiben.", "help.minimum_password_length": "Dein Passwort muss mindestens %1 Zeichen lang sein.", @@ -16,8 +16,8 @@ "alternative_registration": "Alternative Registrierung", "terms_of_use": "Nutzungsbedingungen", "agree_to_terms_of_use": "Ich stimme den Nutzungsbedingungen zu", - "terms_of_use_error": "You must agree to the Terms of Use", + "terms_of_use_error": "Du musst den Nutzungsbedingungen zustimmen", "registration-added-to-queue": "Deine Registration wurde abgeschickt. Du wirst eine E-Mail erhalten, sobald sie von einem Administrator akzeptiert wird.", - "interstitial.intro": "We require some additional information before we can create your account.", - "interstitial.errors-found": "We could not complete your registration:" + "interstitial.intro": "Wir benötigen ein wenig mehr Informationen bevor wir deinen Account erstellen können.", + "interstitial.errors-found": "Wir konnten deinen Registrierungsvorgang nicht abschließen:" } \ No newline at end of file diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 0b79d81e23..965f4a00c2 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -20,7 +20,7 @@ "email-taken": "این ایمیل گرفته شده است.", "email-not-confirmed": "ایمیل شما تاکنون تایید نشده است، برای تایید ایمیل خود را اینجا را کلیک کنید.", "email-not-confirmed-chat": "شما تا قبل از تایید ایمیل قادر به چت نیستید، لطفا برای تایید ایمیل خود اینجا کلیک کنید", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "ایمیل شما هنوز تایید نشده است، لطفا صندوق پیام های خود را برای تایید ایمیل بررسی کنید.", "no-email-to-confirm": "ایمیل شما تایید نشده است ، لطفا برای وارد کردن ایمیل اینجا کلیک کنید", "email-confirm-failed": "سیستم موفق به تایید ایمیل شما نشد، لطفا بعدا دوباره سعی کنید", "confirm-email-already-sent": "ایمیل فعال‌سازی قبلا فرستاده شده، لطفا %1 دقیقه صبر کنید تا ایمیل دیگری بفرستید.", diff --git a/public/language/fa_IR/modules.json b/public/language/fa_IR/modules.json index 20f1a0d8d1..9b39f7cb7f 100644 --- a/public/language/fa_IR/modules.json +++ b/public/language/fa_IR/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "عکس", "composer.upload-picture": "بارگذاری عکس", "composer.upload-file": "بارگذاری فایل", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "حالت ذن", "bootbox.ok": "باشه", "bootbox.cancel": "انصراف", "bootbox.confirm": "تایید", diff --git a/public/language/fa_IR/topic.json b/public/language/fa_IR/topic.json index adaf0d29f6..f589a051ef 100644 --- a/public/language/fa_IR/topic.json +++ b/public/language/fa_IR/topic.json @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "این موضوع جابه‌جا خواهد شد به دستهٔ", "fork_topic_instruction": "پست‌هایی را که می‌خواهید به موضوع تازه ببرید، انتخاب کنید", "fork_no_pids": "هیچ پستی انتخاب نشده!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 پست (ها) انتخاب شده اند", "fork_success": "موضوع با موفقیت منشعب شد! برای رفتن به موضوع انشعابی اینجا را کلیک کنید.", "delete_posts_instruction": "با کلیک بر روی پست شما می خواهید به حذف/پاکسازی", "composer.title_placeholder": "عنوان موضوعتان را اینجا بنویسید...", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 27385670ed..48811f7373 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -20,7 +20,7 @@ "email-taken": "E-posta Alınmış", "email-not-confirmed": "E-postanız onaylanmamış, onaylamak için lütfen buraya tıklayın.", "email-not-confirmed-chat": "E-postanız onaylanana kadar sohbet edemezsiniz, onaylamak için lütfen buraya tıklayın.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "E-postanız onaylanmamış, onaylamak için lütfen gelen kutunuzu kontrol edin.", "no-email-to-confirm": "Bu forum e-posta doğrulaması gerektirir, lütfen buraya bir e-posta adresi girin", "email-confirm-failed": "E-posta adresinizi doğrulayamıyoruz. Lütfen daha sonra tekrar deneyin.", "confirm-email-already-sent": "E-mail onayı zaten gönderilmiş, yeni bir onay göndermek için lütfen 1 dakika bekleyin.", diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json index 059d8670fb..0edc53c346 100644 --- a/public/language/tr/modules.json +++ b/public/language/tr/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Görsel", "composer.upload-picture": "Görsel Yükle", "composer.upload-file": "Dosya Yükle", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Zen Modu", "bootbox.ok": "Tamam", "bootbox.cancel": "İptal", "bootbox.confirm": "Onayla", From c8a369f5c3efd102583c9a5f579075cd406dc660 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 13 Aug 2016 20:02:48 +0300 Subject: [PATCH 046/386] closes #4939 --- src/database/redis/sorted.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 0341c043f7..cd1587f494 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -110,7 +110,7 @@ module.exports = function(redisClient, module) { } var objects = []; for(var i=0; i Date: Sat, 13 Aug 2016 20:20:13 +0300 Subject: [PATCH 047/386] added database to issue template --- .github/ISSUE_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index cc592ac143..28dd965678 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,7 @@ Please include the following information when submitting a bug report/issue: * NodeBB version and git hash (to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory) +* Database (mongo or redis) and it's version. * Exact steps to cause this issue 1. First I did this... 2. Then, I clicked on this item... @@ -9,4 +10,4 @@ Please include the following information when submitting a bug report/issue: * What happened instead * e.g. Instead, I got *zyx* and NodeBB set fire to my house -Thank you! \ No newline at end of file +Thank you! From a7ed5a0129f808a7765c6b7c39eb63f5ad239336 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 14 Aug 2016 12:59:03 +0300 Subject: [PATCH 048/386] delete content --- public/src/client/account/header.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 616cebbe82..6b39b7f2da 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -152,7 +152,7 @@ define('forum/account/header', [ return; } - socket.emit('admin.user.deleteUsers', [ajaxify.data.theirid], function(err) { + socket.emit('admin.user.deleteUsersAndContent', [ajaxify.data.theirid], function(err) { if (err) { return app.alertError(err.message); } From aad9f83869f07adbf1bd35365b306538ce16ef28 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 14 Aug 2016 13:13:15 +0300 Subject: [PATCH 049/386] use getSortedRevRangeWithScores, cleanup --- src/user/info.js | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/user/info.js b/src/user/info.js index a9ca675eab..da85e25116 100644 --- a/src/user/info.js +++ b/src/user/info.js @@ -1,33 +1,39 @@ 'use strict'; -var async = require('async'), - _ = require('underscore'); +var async = require('async'); +var _ = require('underscore'); -var db = require('../database'), - posts = require('../posts'), - topics = require('../topics'); +var db = require('../database'); +var posts = require('../posts'); +var topics = require('../topics'); module.exports = function(User) { User.getModerationHistory = function(uid, callback) { async.waterfall([ function(next) { async.parallel({ - flags: async.apply(db.getSortedSetRevRangeByScoreWithScores, 'uid:' + uid + ':flag:pids', 0, 20, '+inf', '-inf'), - bans: async.apply(db.getSortedSetRevRangeByScoreWithScores, 'uid:' + uid + ':bans', 0, 20, '+inf', '-inf') + flags: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':flag:pids', 0, 19), + bans: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':bans', 0,19) }, next); }, - async.apply(getFlagMetadata), - async.apply(formatBanData) + function(data, next) { + getFlagMetadata(data, next); + } ], function(err, data) { - callback(err, data); + if (err) { + return callback(err); + } + formatBanData(data); + callback(null, data); }); }; function getFlagMetadata(data, callback) { - // Retrieve post title & slug from flags list - posts.getPostsFields(data.flags.map(function(flagObj) { + var pids = data.flags.map(function(flagObj) { return parseInt(flagObj.value, 10); - }), ['tid'], function(err, postData) { + }); + + posts.getPostsFields(pids, ['tid'], function(err, postData) { if (err) { return callback(err); } @@ -37,6 +43,9 @@ module.exports = function(User) { }); topics.getTopicsFields(tids, ['title'], function(err, topicData) { + if (err) { + return callback(err); + } data.flags = data.flags.map(function(flagObj, idx) { flagObj.pid = flagObj.value; flagObj.timestamp = flagObj.score; @@ -54,7 +63,7 @@ module.exports = function(User) { }); } - function formatBanData(data, callback) { + function formatBanData(data) { data.bans = data.bans.map(function(banObj) { banObj.until = parseInt(banObj.value, 10); banObj.untilReadable = new Date(banObj.until).toString(); @@ -67,7 +76,5 @@ module.exports = function(User) { return banObj; }); - - setImmediate(callback, null, data); } -} \ No newline at end of file +}; \ No newline at end of file From c58e23db58f41d8d3e741d20d9ca35d02c0ad6b5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 14 Aug 2016 19:12:33 +0300 Subject: [PATCH 050/386] fix tests --- src/groups.js | 1 + tests/categories.js | 2 +- tests/database/hash.js | 5 +---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/groups.js b/src/groups.js index e572c30110..bd6e122d33 100644 --- a/src/groups.js +++ b/src/groups.js @@ -182,6 +182,7 @@ var utils = require('../public/src/utils'); results.base.deleted = !!parseInt(results.base.deleted, 10); results.base.hidden = !!parseInt(results.base.hidden, 10); results.base.system = !!parseInt(results.base.system, 10); + results.base.memberCount = parseInt(results.base.memberCount, 10); results.base.private = (results.base.private === null || results.base.private === undefined) ? true : !!parseInt(results.base.private, 10); results.base.disableJoinRequests = parseInt(results.base.disableJoinRequests, 10) === 1; results.base.isMember = results.isMember; diff --git a/tests/categories.js b/tests/categories.js index 4ec06ab3b4..c5bbee22cc 100644 --- a/tests/categories.js +++ b/tests/categories.js @@ -38,7 +38,7 @@ describe('Categories', function() { set: 'cid:' + categoryObj.cid + ':tids', reverse: true, start: 0, - end: -1, + stop: -1, uid: 0 }, function(err, categoryData) { assert(categoryData); diff --git a/tests/database/hash.js b/tests/database/hash.js index a178280e3c..7bcd23f5d8 100644 --- a/tests/database/hash.js +++ b/tests/database/hash.js @@ -202,10 +202,7 @@ describe('Hash methods', function() { assert.equal(err, null); assert.equal(arguments.length, 2); assert.equal(Array.isArray(values) && values.length === 3, true); - values.forEach(function(value) { - assert.notEqual(['baris', 'usakli', 99].indexOf(value), -1); - }); - + assert.deepEqual(['baris', 'usakli', 99].sort(), values.sort()); done(); }); }); From 49bf27d98548eafe804cfba78d882e3565a5bd09 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 14 Aug 2016 21:40:58 +0300 Subject: [PATCH 051/386] closes #4944 --- public/src/client/topic/fork.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/public/src/client/topic/fork.js b/public/src/client/topic/fork.js index 2083a4867e..ca30f157b0 100644 --- a/public/src/client/topic/fork.js +++ b/public/src/client/topic/fork.js @@ -4,9 +4,9 @@ define('forum/topic/fork', ['components', 'postSelect'], function(components, postSelect) { - var Fork = {}, - forkModal, - forkCommit; + var Fork = {}; + var forkModal; + var forkCommit; Fork.init = function() { $('.topic').on('click', '[component="topic/fork"]', onForkThreadClicked); @@ -95,7 +95,10 @@ define('forum/topic/fork', ['components', 'postSelect'], function(components, po components.get('post', 'pid', pid).toggleClass('bg-success', false); }); - forkModal.remove(); + if (forkModal) { + forkModal.remove(); + forkModal = null; + } components.get('topic').off('click', '[data-pid]'); postSelect.enableClicksOnPosts(); From 96abdb4b69b0848be1018e7c9e708fd25028d13a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 15 Aug 2016 09:26:06 -0400 Subject: [PATCH 052/386] Up composer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d457b471a..d600e4661c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.1.7", + "nodebb-plugin-composer-default": "4.1.8", "nodebb-plugin-dbsearch": "1.0.2", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", From b9961bcffa1721856af6a9336aa9a9da7dba07ae Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 15 Aug 2016 19:08:08 +0300 Subject: [PATCH 053/386] if email is undefined use empty string --- src/user/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/create.js b/src/user/create.js index bd79270a93..df504c3da4 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -26,7 +26,7 @@ module.exports = function(User) { var userData = { 'username': data.username, 'userslug': data.userslug, - 'email': data.email, + 'email': data.email || '', 'joindate': timestamp, 'lastonline': timestamp, 'picture': '', From 6022fd984a4169a2c259facc4645d2af9da683ee Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 15 Aug 2016 19:23:10 +0300 Subject: [PATCH 054/386] closes #4945 --- src/database/mongo/sorted.js | 80 ++++++++++++++++++++++++++ src/database/redis/sorted.js | 63 +++++++++++++++++++-- tests/database/sorted.js | 105 +++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 5 deletions(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 9e9f72969e..a262ff9984 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -595,4 +595,84 @@ module.exports = function(db, module) { callback ); }; + + + module.getSortedSetIntersect = function(params, callback) { + params.sort = 1; + getSortedSetRevIntersect(params, callback); + }; + + module.getSortedSetRevIntersect = function(params, callback) { + params.sort = -1; + getSortedSetRevIntersect(params, callback); + }; + + function getSortedSetRevIntersect (params, callback) { + var sets = params.sets; + var start = params.hasOwnProperty('start') ? params.start : 0; + var stop = params.hasOwnProperty('stop') ? params.stop : -1; + var weights = params.weights || []; + var aggregate = {}; + + if (params.aggregate) { + aggregate['$' + params.aggregate.toLowerCase()] = '$score'; + } else { + aggregate.$sum = '$score'; + } + + var limit = stop - start + 1; + if (limit <= 0) { + limit = 0; + } + + var pipeline = []; + + pipeline.push({ $match: { _key: {$in: sets}} }); + + weights.forEach(function(weight, index) { + if (weight !== 1) { + pipeline.push({ + $project: { + value: 1, + score: { + $cond: { if: { $eq: [ "$_key", sets[index] ] }, then: { $multiply: [ '$score', weight ] }, else: '$score' } + } + } + }); + } + }); + + pipeline.push({ $group: { _id: {value: '$value'}, totalScore: aggregate, count: {$sum: 1}} }); + pipeline.push({ $match: { count: sets.length} }); + pipeline.push({ $sort: { totalScore: params.sort} }); + + if (start) { + pipeline.push({ $skip: start }); + } + + if (limit > 0) { + pipeline.push({ $limit: limit }); + } + + var project = { _id: 0, value: '$_id.value'}; + if (params.withScores) { + project.score = '$totalScore'; + } + pipeline.push({ $project: project }); + + db.collection('objects').aggregate(pipeline, function(err, data) { + if (err || !data) { + return callback(err); + } + + if (!params.withScores) { + data = data.map(function(item) { + return item.value; + }); + } + + callback(null, data); + }); + } + }; diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index cd1587f494..d97b6b60cc 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -29,7 +29,7 @@ module.exports = function(redisClient, module) { args.push(scores[i], values[i]); } - redisClient.zadd(args, function(err, res) { + redisClient.zadd(args, function(err) { callback(err); }); } @@ -42,7 +42,7 @@ module.exports = function(redisClient, module) { multi.zadd(keys[i], score, value); } - multi.exec(function(err, res) { + multi.exec(function(err) { callback(err); }); }; @@ -53,13 +53,13 @@ module.exports = function(redisClient, module) { value = [value]; } - helpers.multiKeyValues(redisClient, 'zrem', key, value, function(err, result) { + helpers.multiKeyValues(redisClient, 'zrem', key, value, function(err) { callback(err); }); }; module.sortedSetsRemove = function(keys, value, callback) { - helpers.multiKeysValue(redisClient, 'zrem', keys, value, function(err, result) { + helpers.multiKeysValue(redisClient, 'zrem', keys, value, function(err) { callback(err); }); }; @@ -70,7 +70,7 @@ module.exports = function(redisClient, module) { for(var i=0; i Date: Mon, 15 Aug 2016 19:26:18 +0300 Subject: [PATCH 055/386] shorter --- src/database/mongo/sorted.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index a262ff9984..1f9a67adb1 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -625,9 +625,7 @@ module.exports = function(db, module) { limit = 0; } - var pipeline = []; - - pipeline.push({ $match: { _key: {$in: sets}} }); + var pipeline = [{ $match: { _key: {$in: sets}} }]; weights.forEach(function(weight, index) { if (weight !== 1) { From 3d56776ab070f80c34367f0f5e4ad806b8e8c2b6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 15 Aug 2016 19:28:01 +0300 Subject: [PATCH 056/386] fix tabs --- src/database/redis/sorted.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index d97b6b60cc..eaa3b2430f 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -249,7 +249,7 @@ module.exports = function(redisClient, module) { params.push('WITHSCORES'); } - var multi = redisClient.multi(); + var multi = redisClient.multi(); multi.zunionstore([tempSetName, sets.length].concat(sets)); multi[method](params); multi.del(tempSetName); @@ -315,7 +315,7 @@ module.exports = function(redisClient, module) { rangeParams.push('WITHSCORES'); } - var multi = redisClient.multi(); + var multi = redisClient.multi(); multi.zinterstore(interParams); multi[params.method](rangeParams); multi.del(tempSetName); From ebbfe3cc1f21a60633f0b7c4d70124032a23519c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Aug 2016 20:21:32 +0300 Subject: [PATCH 057/386] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 200a444b6d..2d1c90fed8 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ Additional functionality is enabled through the use of third-party plugins. [![](http://i.imgur.com/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png) [![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg) -[![](http://i.imgur.com/8OLssij.png)](http://i.imgur.com/8OLssij.png) -[![](http://i.imgur.com/JKOc0LZ.png)](http://i.imgur.com/JKOc0LZ.png) +[![](http://i.imgur.com/HwNEXGu.png)](http://i.imgur.com/HwNEXGu.png) +[![](http://i.imgur.com/II1byYs.png)](http://i.imgur.com/II1byYs.png) + + ## How can I follow along/contribute? From a002eecd038bd8f17f3d503d5ed5e07f210d0c9c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 12:16:32 +0300 Subject: [PATCH 058/386] dont crash if set is not defined, closes https://github.com/NodeBB/nodebb-plugin-leaderboard/issues/4 --- src/controllers/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/users.js b/src/controllers/users.js index 4ee7e5a9ec..20ea62ca40 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -102,6 +102,10 @@ usersController.getUsers = function(set, uid, page, callback) { 'users:flags': {title: '[[pages:users/most-flags]]', crumb: '[[users:most_flags]]'}, }; + if (!setToData[set]) { + setToData[set] = {title: '', crumb: ''}; + } + var breadcrumbs = [{text: setToData[set].crumb}]; if (set !== 'users:joindate') { From ba008a2fa5bc5a0dbbe69c6bbd74190f9d658a1e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 16 Aug 2016 11:43:44 -0400 Subject: [PATCH 059/386] fixes #4949 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d600e4661c..346176c76e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "cron": "^1.0.5", "csurf": "^1.6.1", "daemon": "~1.1.0", - "express": "^4.9.5", + "express": "^4.14.0", "express-session": "^1.8.2", "express-useragent": "0.2.4", "html-to-text": "2.0.0", @@ -74,7 +74,7 @@ "semver": "^5.1.0", "serve-favicon": "^2.1.5", "sitemap": "^1.4.0", - "socket.io": "^1.4.0", + "socket.io": "^1.4.8", "socket.io-client": "^1.4.0", "socket.io-redis": "^1.0.0", "socketio-wildcard": "~0.3.0", From 316187946fc0b41cecaca3c09c364e8b1051bc47 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 19:17:21 +0300 Subject: [PATCH 060/386] fix related crash --- src/topics.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/topics.js b/src/topics.js index 0079c501ce..4cb795b2c3 100644 --- a/src/topics.js +++ b/src/topics.js @@ -188,19 +188,27 @@ var social = require('./social'); posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse), category: async.apply(Topics.getCategoryData, topicData.tid), threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}), - tags: async.apply(Topics.getTopicTagsObjects, topicData.tid), isFollowing: async.apply(Topics.isFollowing, [topicData.tid], uid), isIgnoring: async.apply(Topics.isIgnoring, [topicData.tid], uid), bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid), postSharing: async.apply(social.getActivePostSharing), - related: async.apply(Topics.getRelatedTopics, topicData, uid) + related: function(next) { + async.waterfall([ + function(next) { + Topics.getTopicTagsObjects(topicData.tid, next); + }, + function(tags, next) { + topicData.tags = tags; + Topics.getRelatedTopics(topicData, uid, next); + } + ], next); + } }, next); }, function (results, next) { topicData.posts = results.posts; topicData.category = results.category; topicData.thread_tools = results.threadTools.tools; - topicData.tags = results.tags; topicData.isFollowing = results.isFollowing[0]; topicData.isNotFollowing = !results.isFollowing[0] && !results.isIgnoring[0]; topicData.isIgnoring = results.isIgnoring[0]; From f77d9053add47a7e4ffd74ab9a1d8007b8746170 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 19:52:22 +0300 Subject: [PATCH 061/386] closes #4795 --- public/src/admin/manage/tags.js | 38 ++++++++++++++++++++++++++++++++- src/controllers/tags.js | 1 - src/database/mongo/sorted.js | 7 +++--- src/socket.io/admin/tags.js | 12 +++++++++-- src/topics/tags.js | 27 +++++++++++++++++++++-- src/views/admin/manage/tags.tpl | 27 +++++++++++++++++++++-- tests/database/sorted.js | 12 +++++++++++ 7 files changed, 113 insertions(+), 11 deletions(-) diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index 01f755612b..9fda81eccb 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -1,5 +1,5 @@ "use strict"; -/*global define, socket, app, utils, bootbox*/ +/*global define, socket, app, utils, bootbox, ajaxify*/ define('admin/manage/tags', [ 'forum/infinitescroll', @@ -12,11 +12,47 @@ define('admin/manage/tags', [ Tags.init = function() { selectable.enable('.tag-management', '.tag-row'); + handleCreate(); handleSearch(); handleModify(); handleDeleteSelected(); }; + function handleCreate() { + var createModal = $('#create-modal'); + var createTagName = $('#create-tag-name'); + var createModalGo = $('#create-modal-go'); + + createModal.on('keypress', function(e) { + if (e.keyCode === 13) { + createModalGo.click(); + } + }); + + $('#create').on('click', function() { + createModal.modal('show'); + setTimeout(function() { + createTagName.focus(); + }, 250); + }); + + createModalGo.on('click', function() { + socket.emit('admin.tags.create', { + tag: createTagName.val() + }, function(err) { + if (err) { + return app.alertError(err.message); + } + + createTagName.val(''); + createModal.on('hidden.bs.modal', function() { + ajaxify.refresh(); + }); + createModal.modal('hide'); + }); + }); + } + function handleSearch() { $('#tag-search').on('input propertychange', function() { if (timeoutId) { diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 45ab3424aa..163efc2af0 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -44,7 +44,6 @@ tagsController.getTag = function(req, res, next) { }, function (results, next) { if (Array.isArray(results.tids) && !results.tids.length) { - topics.deleteTag(req.params.tag); return res.render('tag', templateData); } topicCount = results.topicCount; diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 1f9a67adb1..5cc06eab42 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -1,6 +1,7 @@ "use strict"; var async = require('async'); +var utils = require('../../../public/src/utils'); module.exports = function(db, module) { var helpers = module.helpers.mongo; @@ -381,12 +382,12 @@ module.exports = function(db, module) { map[item.value] = item.score; }); - var returnData = new Array(values.length), - score; + var returnData = new Array(values.length); + var score; for(var i=0; i Your forum does not have any topics with tags yet. - +
    @@ -32,9 +32,10 @@
    -
    Modify Tag
    +
    Create & Modify Tags

    Select tags via clicking and/or dragging, use shift to select multiple.

    +
    @@ -48,4 +49,26 @@
    + +
    diff --git a/tests/database/sorted.js b/tests/database/sorted.js index 8f23038dae..c1066f5ff4 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -336,6 +336,18 @@ describe('Sorted Set methods', function() { }); describe('sortedSetScores()', function() { + before(function(done) { + db.sortedSetAdd('zeroScore', 0, 'value1', done); + }); + + it('should return 0 if score is 0', function(done) { + db.sortedSetScores('zeroScore', ['value1'], function(err, scores) { + assert.ifError(err); + assert.equal(0, scores[0]); + done(); + }); + }); + it('should return the scores of value in sorted sets', function(done) { db.sortedSetScores('sortedSetTest1', ['value2', 'value1', 'doesnotexist'], function(err, scores) { assert.equal(err, null); From 204dd2a69f6b7ead4e4a46a3b9cff3b833091789 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 20:11:03 +0300 Subject: [PATCH 062/386] closes #4948 --- loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 56e4864289..78e3f34a7f 100644 --- a/loader.js +++ b/loader.js @@ -207,7 +207,8 @@ function getPorts() { Loader.restart = function() { killWorkers(); - + nconf.remove('file'); + nconf.use('file', { file: path.join(__dirname, '/config.json') }); Loader.start(); }; From e26166a45b268d041c539af86ee32e8d6d25d53f Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 16 Aug 2016 13:48:53 -0400 Subject: [PATCH 063/386] closes #4950 --- public/language/en_GB/error.json | 5 ++++- public/src/app.js | 26 ++++++++++++++++++++++++++ public/src/sockets.js | 6 ++++++ src/socket.io/index.js | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 319f5005b6..b6ddd1a2bb 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -153,5 +153,8 @@ "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", "no-users-selected": "No user(s) selected", - "invalid-home-page-route": "Invalid home page route" + "invalid-home-page-route": "Invalid home page route", + + "invalid-session": "Session Mismatch", + "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." } diff --git a/public/src/app.js b/public/src/app.js index a913517363..ecb17de0c9 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -117,6 +117,10 @@ app.cacheBuster = null; }; app.alertError = function (message, timeout) { + if (message === '[[error:invalid-session]]') { + return app.handleInvalidSession(); + } + app.alert({ title: '[[global:alert.error]]', message: message, @@ -125,6 +129,28 @@ app.cacheBuster = null; }); }; + app.handleInvalidSession = function() { + if (app.flags && app.flags._sessionRefresh) { + return; + } + + app.flags = app.flags || {}; + app.flags._sessionRefresh = true; + + require(['translator'], function(translator) { + translator.translate('[[error:invalid-session-text]]', function(translated) { + bootbox.alert({ + title: '[[error:invalid-session]]', + message: translated, + closeButton: false, + callback: function() { + window.location.reload(); + } + }); + }); + }); + }; + app.enterRoom = function (room, callback) { callback = callback || function() {}; if (socket && app.user.uid && app.currentRoom !== room) { diff --git a/public/src/sockets.js b/public/src/sockets.js index 461c148be7..f2a9bfd346 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -28,6 +28,12 @@ app.isConnected = false; setTimeout(socket.connect.bind(socket), parseInt(config.reconnectionDelay, 10) * 10); }); + socket.on('checkSession', function(uid) { + if (parseInt(uid, 10) !== parseInt(app.user.uid, 10)) { + app.handleInvalidSession(); + } + }); + socket.on('event:banned', onEventBanned); socket.on('event:alert', app.alert); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index cebbfe1a24..3ec75ecd0d 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -56,6 +56,8 @@ var ratelimit = require('../middleware/ratelimit'); } else { socket.join('online_guests'); } + + io.sockets.sockets[socket.id].emit('checkSession', socket.uid); } function onMessage(socket, payload) { From 3b0eca3be2a43cd396fd59208dde506cca08e3bb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 22:27:21 +0300 Subject: [PATCH 064/386] fix timestamp on posts.reply --- src/socket.io/posts.js | 1 + src/topics/create.js | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 3251c3700c..6c86458fc2 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -30,6 +30,7 @@ SocketPosts.reply = function(socket, data, callback) { data.uid = socket.uid; data.req = websockets.reqFromSocket(socket); + data.timestamp = Date.now(); topics.reply(data, function(err, postData) { if (err) { diff --git a/src/topics/create.js b/src/topics/create.js index cd1bf23a62..65f829c694 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -230,7 +230,15 @@ module.exports = function(Topics) { check(content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next); }, function(next) { - posts.create({uid: uid, tid: tid, handle: data.handle, content: content, toPid: data.toPid, timestamp: data.timestamp, ip: data.req ? data.req.ip : null}, next); + posts.create({ + uid: uid, + tid: tid, + handle: data.handle, + content: content, + toPid: data.toPid, + timestamp: data.timestamp, + ip: data.req ? data.req.ip : null + }, next); }, function(_postData, next) { postData = _postData; From 6e469f1f0a14b3a2a2e3aad8e3062197deccd5cb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 22:30:11 +0300 Subject: [PATCH 065/386] add missing return to guestHandleValid --- src/topics/create.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/topics/create.js b/src/topics/create.js index 65f829c694..ea7d35761a 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -336,6 +336,7 @@ module.exports = function(Topics) { } callback(); }); + return; } callback(); } From 9207d6a74d3ba443a6f4a73e9596d89a7b2e9d33 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 16 Aug 2016 23:27:43 +0300 Subject: [PATCH 066/386] fix so the parent button doesn't ajaxify if the post is already on the screen --- public/src/client/topic.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/src/client/topic.js b/public/src/client/topic.js index aff00909d1..5f58f38e0b 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -198,7 +198,8 @@ define('forum/topic', [ var toPost = $('[component="post"][data-pid="' + toPid + '"]'); if (toPost.length) { e.preventDefault(); - return navigator.scrollToPost(toPost.attr('data-index'), true); + navigator.scrollToPost(toPost.attr('data-index'), true); + return false; } }); } From 4af631666add967a7c7cb6e8bf18c87d0c19a4d6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Aug 2016 12:12:07 +0300 Subject: [PATCH 067/386] closes #4955 --- src/topics/delete.js | 71 ++++++++++++++++++++++++++++++-------------- tests/topics.js | 28 +++++++++++++---- 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/topics/delete.js b/src/topics/delete.js index a960ff0421..cf6ae01d8d 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -32,7 +32,7 @@ module.exports = function(Topics) { db.sortedSetRemove('cid:' + topicData.cid + ':pids', pids, next); }); } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -79,7 +79,7 @@ module.exports = function(Topics) { }); }); } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -109,28 +109,35 @@ module.exports = function(Topics) { }; Topics.purge = function(tid, uid, callback) { - async.parallel([ + async.waterfall([ function(next) { - db.deleteAll([ - 'tid:' + tid + ':followers', - 'tid:' + tid + ':ignorers', - 'tid:' + tid + ':posts', - 'tid:' + tid + ':posts:votes', - 'tid:' + tid + ':bookmarks', - 'tid:' + tid + ':posters' + deleteFromFollowersIgnorers(tid, next); + }, + function(next) { + async.parallel([ + function(next) { + db.deleteAll([ + 'tid:' + tid + ':followers', + 'tid:' + tid + ':ignorers', + 'tid:' + tid + ':posts', + 'tid:' + tid + ':posts:votes', + 'tid:' + tid + ':bookmarks', + 'tid:' + tid + ':posters' + ], next); + }, + function(next) { + db.sortedSetsRemove(['topics:tid', 'topics:recent', 'topics:posts', 'topics:views'], tid, next); + }, + function(next) { + deleteTopicFromCategoryAndUser(tid, next); + }, + function(next) { + Topics.deleteTopicTags(tid, next); + }, + function(next) { + reduceCounters(tid, next); + } ], next); - }, - function(next) { - db.sortedSetsRemove(['topics:tid', 'topics:recent', 'topics:posts', 'topics:views'], tid, next); - }, - function(next) { - deleteTopicFromCategoryAndUser(tid, next); - }, - function(next) { - Topics.deleteTopicTags(tid, next); - }, - function(next) { - reduceCounters(tid, next); } ], function(err) { if (err) { @@ -141,6 +148,26 @@ module.exports = function(Topics) { }); }; + function deleteFromFollowersIgnorers(tid, callback) { + async.waterfall([ + function(next) { + async.parallel({ + followers: async.apply(db.getSetMembers, 'tid:' + tid + ':followers'), + ignorers: async.apply(db.getSetMembers, 'tid:' + tid + ':ignorers') + }, next); + }, + function(results, next) { + var followerKeys = results.followers.map(function(uid) { + return 'uid:' + uid + ':followed_tids'; + }); + var ignorerKeys = results.ignorers.map(function(uid) { + return 'uid:' + uid + 'ignored_tids'; + }); + db.sortedSetsRemove(followerKeys.concat(ignorerKeys), tid, next); + } + ], callback); + } + function deleteTopicFromCategoryAndUser(tid, callback) { Topics.getTopicFields(tid, ['cid', 'uid'], function(err, topicData) { if (err) { diff --git a/tests/topics.js b/tests/topics.js index 7103b32689..bf890f3ebe 100644 --- a/tests/topics.js +++ b/tests/topics.js @@ -188,12 +188,24 @@ describe('Topic\'s', function() { describe('.purge/.delete', function() { var newTopic; - + var followerUid; before(function(done) { - topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { - newTopic = result.topicData; - done(); - }); + async.waterfall([ + function(next) { + topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { + assert.ifError(err); + newTopic = result.topicData; + next(); + }); + }, + function(next) { + User.create({username: 'topicFollower', password: '123456'}, next); + }, + function(_uid, next) { + followerUid = _uid; + topics.follow(newTopic.tid, _uid, next); + } + ], done); }); it('should delete the topic', function(done) { @@ -206,7 +218,11 @@ describe('Topic\'s', function() { it('should purge the topic', function(done) { topics.purge(newTopic.tid, 1, function(err) { assert.ifError(err); - done(); + db.isSortedSetMember('uid:' + followerUid + ':followed_tids', newTopic.tid, function(err, isMember) { + assert.ifError(err); + assert.strictEqual(false, isMember); + done(); + }); }); }); }); From d43c19c1736a807cdedb05cc808d2191f05683a4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Aug 2016 12:40:14 +0300 Subject: [PATCH 068/386] closes #4952 --- public/src/app.js | 4 +++- public/src/client/search.js | 12 ++---------- public/src/modules/search.js | 8 ++++++++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index ecb17de0c9..7a3a13d080 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -435,7 +435,9 @@ app.cacheBuster = null; $('#search-form').on('submit', function () { var input = $(this).find('input'); require(['search'], function(search) { - search.query({term: input.val()}, function() { + var data = search.getSearchPreferences(); + data.term = input.val(); + search.query(data, function() { input.val(''); }); }); diff --git a/public/src/client/search.js b/public/src/client/search.js index 6ca5b8dc41..bf07ad70b6 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -1,6 +1,6 @@ 'use strict'; -/* globals app, define, utils, socket*/ +/* globals app, define, utils*/ define('forum/search', ['search', 'autocomplete'], function(searchModule, autocomplete) { var Search = {}; @@ -67,7 +67,7 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco function fillOutForm() { var params = utils.params(); - var searchData = getSearchPreferences(); + var searchData = searchModule.getSearchPreferences(); params = utils.merge(searchData, params); if (params) { @@ -156,14 +156,6 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco }); } - function getSearchPreferences() { - try { - return JSON.parse(localStorage.getItem('search-preferences')); - } catch(e) { - return {}; - } - } - function enableAutoComplete() { autocomplete.user($('#posted-by-user')); } diff --git a/public/src/modules/search.js b/public/src/modules/search.js index e24ecbed61..e376c30639 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -73,6 +73,14 @@ define('search', ['navigator', 'translator'], function(nav, translator) { return decodeURIComponent($.param(query)); } + Search.getSearchPreferences = function() { + try { + return JSON.parse(localStorage.getItem('search-preferences')); + } catch(e) { + return {}; + } + }; + Search.queryTopic = function(tid, term, callback) { socket.emit('topics.search', { tid: tid, From ce9ee62fa00201e0098195357a190a83ceb6636e Mon Sep 17 00:00:00 2001 From: Mathias Schreck Date: Tue, 16 Aug 2016 19:46:59 +0200 Subject: [PATCH 069/386] Handle callback errors --- app.js | 15 +++++++++++++++ install/web.js | 4 ++++ public/src/admin/admin.js | 2 +- public/src/admin/advanced/errors.js | 4 ++++ public/src/admin/advanced/logs.js | 2 +- public/src/admin/appearance/customise.js | 4 ++-- public/src/admin/extend/plugins.js | 3 +++ public/src/admin/general/dashboard.js | 3 +++ public/src/admin/manage/groups.js | 4 ++++ public/src/admin/manage/ip-blacklist.js | 4 ++++ public/src/admin/settings/email.js | 4 ++-- public/src/app.js | 2 +- public/src/client/category.js | 4 ++++ public/src/client/categoryTools.js | 4 ++++ public/src/client/groups/details.js | 4 ++-- public/src/client/topic.js | 3 +++ public/src/client/users.js | 4 ++++ public/src/modules/notifications.js | 4 ++++ public/src/modules/search.js | 4 ++++ public/src/modules/settings.js | 20 ++++++++++++++------ public/src/utils.js | 8 ++++++++ src/controllers/admin/categories.js | 4 ++++ src/controllers/admin/errors.js | 12 ++++++++++-- src/controllers/admin/homepage.js | 4 ++++ src/controllers/admin/settings.js | 14 +++++++++++++- src/controllers/admin/sounds.js | 4 ++++ src/controllers/admin/uploads.js | 4 ++++ src/controllers/authentication.js | 12 ++++++++++++ src/controllers/index.js | 8 ++++++++ src/groups/cover.js | 5 +++++ src/install.js | 3 +++ src/languages.js | 4 ++++ src/meta/css.js | 4 ++++ src/meta/errors.js | 4 ++++ src/meta/js.js | 4 ++++ src/meta/tags.js | 4 ++++ src/meta/themes.js | 4 ++++ src/middleware/middleware.js | 4 ++++ src/plugins.js | 12 ++++++++++-- src/plugins/install.js | 4 ++++ src/plugins/load.js | 4 ++++ src/posts/flags.js | 4 ++++ src/posts/summary.js | 4 ++++ src/reset.js | 7 ++++++- src/rewards/admin.js | 8 ++++++++ src/rewards/index.js | 10 +++++++++- src/routes/debug.js | 17 +++++++++++++++++ src/socket.io/helpers.js | 8 ++++++++ src/socket.io/user.js | 4 ++++ src/socket.io/user/picture.js | 4 ++++ src/socket.io/user/profile.js | 4 ++++ src/upgrade.js | 24 ++++++++++++++++++++++++ src/user/auth.js | 2 +- src/user/digest.js | 4 ++++ src/user/follow.js | 4 ++++ src/user/picture.js | 4 ++++ src/user/profile.js | 4 ++++ tests/categories.js | 7 +++++++ tests/database/list.js | 2 ++ tests/groups.js | 2 ++ tests/messaging.js | 4 ++++ tests/mocks/databasemock.js | 4 ++++ tests/topics.js | 20 ++++++++++++++++++++ tests/user.js | 7 +++++++ 64 files changed, 359 insertions(+), 23 deletions(-) diff --git a/app.js b/app.js index 6fd215ad61..2158d61282 100644 --- a/app.js +++ b/app.js @@ -285,6 +285,11 @@ function upgrade() { function activate() { require('./src/database').init(function(err) { + if (err) { + winston.error(err.stack); + process.exit(1); + } + var plugin = nconf.get('_')[1] ? nconf.get('_')[1] : nconf.get('activate'), db = require('./src/database'); @@ -296,9 +301,19 @@ function activate() { function listPlugins() { require('./src/database').init(function(err) { + if (err) { + winston.error(err.stack); + process.exit(1); + } + var db = require('./src/database'); db.getSortedSetRange('plugins:active', 0, -1, function(err, plugins) { + if (err) { + winston.error(err.stack); + process.exit(1); + } + winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - ')); process.exit(); }); diff --git a/install/web.js b/install/web.js index 0f4e645e8a..b60614de07 100644 --- a/install/web.js +++ b/install/web.js @@ -130,6 +130,10 @@ function compileLess(callback) { } fs.readFile(path.join(__dirname, '../public/less/install.less'), function(err, style) { + if (err) { + return winston.error('Unable to read LESS install file: ', err); + } + less.render(style.toString(), function(err, css) { if(err) { return winston.error('Unable to compile LESS: ', err); diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index a90967c2e0..add13c30cc 100644 --- a/public/src/admin/admin.js +++ b/public/src/admin/admin.js @@ -82,7 +82,7 @@ socket.emit('admin.restart'); }); - mousetrap.bind('/', function(e) { + mousetrap.bind('/', function(event) { $('#acp-search input').focus(); return false; diff --git a/public/src/admin/advanced/errors.js b/public/src/admin/advanced/errors.js index 52d6427240..3977778b5a 100644 --- a/public/src/admin/advanced/errors.js +++ b/public/src/admin/advanced/errors.js @@ -14,6 +14,10 @@ define('admin/advanced/errors', ['Chart'], function(Chart) { bootbox.confirm('Are you sure you wish to clear the 404 error logs?', function(ok) { if (ok) { socket.emit('admin.errors.clear', {}, function(err) { + if (err) { + return app.alertError(err.message); + } + ajaxify.refresh(); app.alertSuccess('"404 Not Found" errors cleared'); }); diff --git a/public/src/admin/advanced/logs.js b/public/src/admin/advanced/logs.js index ea0503d8a6..8411effabc 100644 --- a/public/src/admin/advanced/logs.js +++ b/public/src/admin/advanced/logs.js @@ -10,7 +10,7 @@ define('admin/advanced/logs', function() { // Affix menu $('.affix').affix(); - $('.logs').find('button[data-action]').on('click', function(e) { + $('.logs').find('button[data-action]').on('click', function(event) { var btnEl = $(this), action = btnEl.attr('data-action'); diff --git a/public/src/admin/appearance/customise.js b/public/src/admin/appearance/customise.js index a85fd663ad..e76bb9d3e4 100644 --- a/public/src/admin/appearance/customise.js +++ b/public/src/admin/appearance/customise.js @@ -15,7 +15,7 @@ define('admin/appearance/customise', ['admin/settings'], function(Settings) { customCSS.setTheme("ace/theme/twilight"); customCSS.getSession().setMode("ace/mode/css"); - customCSS.on('change', function(e) { + customCSS.on('change', function(event) { app.flags = app.flags || {}; app.flags._unsaved = true; $('#customCSS-holder').val(customCSS.getValue()); @@ -24,7 +24,7 @@ define('admin/appearance/customise', ['admin/settings'], function(Settings) { customHTML.setTheme("ace/theme/twilight"); customHTML.getSession().setMode("ace/mode/html"); - customHTML.on('change', function(e) { + customHTML.on('change', function(event) { app.flags = app.flags || {}; app.flags._unsaved = true; $('#customHTML-holder').val(customHTML.getValue()); diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js index b4e8767184..eccb94f527 100644 --- a/public/src/admin/extend/plugins.js +++ b/public/src/admin/extend/plugins.js @@ -20,6 +20,9 @@ define('admin/extend/plugins', function() { pluginID = pluginEl.attr('data-plugin-id'); var btn = $('#' + pluginID + ' [data-action="toggleActive"]'); socket.emit('admin.plugins.toggleActive', pluginID, function(err, status) { + if (err) { + return app.alertError(err); + } btn.html(' ' + (status.active ? 'Deactivate' : 'Activate')); btn.toggleClass('btn-warning', status.active).toggleClass('btn-success', !status.active); diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js index 1224d2ace2..f32cb2d839 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -306,6 +306,9 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { units: units || 'hours', until: until }, function (err, data) { + if (err) { + return app.alertError(err.message); + } if (JSON.stringify(graphData.traffic) === JSON.stringify(data)) { return; } diff --git a/public/src/admin/manage/groups.js b/public/src/admin/manage/groups.js index 8c68837403..3eee1ca08c 100644 --- a/public/src/admin/manage/groups.js +++ b/public/src/admin/manage/groups.js @@ -95,6 +95,10 @@ define('admin/manage/groups', [ sort: 'date' } }, function(err, groups) { + if (err) { + return app.alertError(err.message); + } + templates.parse('admin/manage/groups', 'groups', { groups: groups }, function(html) { diff --git a/public/src/admin/manage/ip-blacklist.js b/public/src/admin/manage/ip-blacklist.js index 9769b3e3bb..949d8ac8fd 100644 --- a/public/src/admin/manage/ip-blacklist.js +++ b/public/src/admin/manage/ip-blacklist.js @@ -29,6 +29,10 @@ define('admin/manage/ip-blacklist', [], function() { socket.emit('blacklist.validate', { rules: blacklist.val() }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + templates.parse('admin/partials/blacklist-validate', data, function(html) { bootbox.alert(html); }); diff --git a/public/src/admin/settings/email.js b/public/src/admin/settings/email.js index a63546f0ba..e6015b3b32 100644 --- a/public/src/admin/settings/email.js +++ b/public/src/admin/settings/email.js @@ -29,7 +29,7 @@ define('admin/settings/email', ['admin/settings'], function(settings) { emailEditor.setTheme("ace/theme/twilight"); emailEditor.getSession().setMode("ace/mode/html"); - emailEditor.on('change', function(e) { + emailEditor.on('change', function(event) { $('#email-editor-holder').val(emailEditor.getValue()); }); @@ -58,4 +58,4 @@ define('admin/settings/email', ['admin/settings'], function(settings) { } return module; -}); \ No newline at end of file +}); diff --git a/public/src/app.js b/public/src/app.js index 7a3a13d080..a23125d5c4 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -40,7 +40,7 @@ app.cacheBuster = null; components.get('user/logout').on('click', app.logout); }); - Visibility.change(function(e, state){ + Visibility.change(function(event, state){ if (state === 'visible') { app.isFocused = true; app.alternatingTitle(''); diff --git a/public/src/client/category.js b/public/src/client/category.js index 98d0063b4b..af0d26b980 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -91,6 +91,10 @@ define('forum/category', [ Category.toBottom = function() { socket.emit('categories.getTopicCount', ajaxify.data.cid, function(err, count) { + if (err) { + return app.alertError(err.message); + } + navigator.scrollBottom(count - 1); }); }; diff --git a/public/src/client/categoryTools.js b/public/src/client/categoryTools.js index 348ff684f8..970ab29d4a 100644 --- a/public/src/client/categoryTools.js +++ b/public/src/client/categoryTools.js @@ -87,6 +87,10 @@ define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', components.get('topic/move-all').on('click', function() { move.init(null, cid, function(err) { + if (err) { + return app.alertError(err.message); + } + ajaxify.refresh(); }); }); diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index c95120ef77..f71b37886e 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -230,7 +230,7 @@ define('forum/groups/details', [ if (ajaxify.data.group.isOwner) { var searchInput = $('[component="groups/members/invite"]'); require(['autocomplete'], function(autocomplete) { - autocomplete.user(searchInput, function(e, selected) { + autocomplete.user(searchInput, function(event, selected) { socket.emit('groups.issueInvite', { toUid: selected.item.user.uid, groupName: ajaxify.data.group.name @@ -259,4 +259,4 @@ define('forum/groups/details', [ } return Details; -}); \ No newline at end of file +}); diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 5f58f38e0b..dcff9d3fe1 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -127,6 +127,9 @@ define('forum/topic', [ Topic.toBottom = function() { socket.emit('topics.postcount', ajaxify.data.tid, function(err, postCount) { + if (err) { + return app.alertError(err.message); + } if (config.topicPostSort !== 'oldest_to_newest') { postCount = 2; } diff --git a/public/src/client/users.js b/public/src/client/users.js index fe083a13e2..40f221e33f 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -47,6 +47,10 @@ define('forum/users', ['translator'], function(translator) { set: set, after: after }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + if (data && data.users.length) { onUsersLoaded(data); $('#load-more-users-btn').removeClass('disabled'); diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 533199c31c..dcabe7d0a1 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -103,6 +103,10 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound, } socket.emit('notifications.getCount', function(err, count) { + if (err) { + return app.alertError(err.message); + } + Notifications.updateNotifCount(count); }); diff --git a/public/src/modules/search.js b/public/src/modules/search.js index e376c30639..be6ac6a711 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -142,6 +142,10 @@ define('search', ['navigator', 'translator'], function(nav, translator) { topicPostSort: config.topicPostSort }; socket.emit('posts.getPidIndex', data, function(err, postIndex) { + if (err) { + return app.alertError(err.message); + } + nav.scrollToPost(postIndex, true); }); } else { diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 79c150854d..692e0ec7b4 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -508,13 +508,21 @@ define('settings', function () { app.flags._unsaved = false; if (typeof callback === 'function') { - callback(); + callback(err); } else { - app.alert({ - title: 'Settings Saved', - type: 'success', - timeout: 2500 - }); + if (err) { + app.alert({ + title: 'Error while saving settings', + type: 'error', + timeout: 2500 + }); + } else { + app.alert({ + title: 'Settings Saved', + type: 'success', + timeout: 2500 + }); + } } }); } diff --git a/public/src/utils.js b/public/src/utils.js index a1ce6959a4..5d44bcd4fa 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -45,8 +45,16 @@ list.forEach(function(file) { file = dir + '/' + file; fs.stat(file, function(err, stat) { + if (err) { + return done(err); + } + if (stat && stat.isDirectory()) { utils.walk(file, function(err, res) { + if (err) { + return done(err); + } + results = results.concat(res); if (!--pending) { done(null, results); diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 04001e0f65..c33dde5c7c 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -48,6 +48,10 @@ categoriesController.getAnalytics = function(req, res, next) { name: async.apply(categories.getCategoryField, req.params.category_id, 'name'), analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id) }, function(err, data) { + if (err) { + return next(err); + } + res.render('admin/manage/category-analytics', data); }); }; diff --git a/src/controllers/admin/errors.js b/src/controllers/admin/errors.js index 4b59932965..d2e29d5eb1 100644 --- a/src/controllers/admin/errors.js +++ b/src/controllers/admin/errors.js @@ -8,20 +8,28 @@ var meta = require('../../meta'), var errorsController = {}; -errorsController.get = function(req, res) { +errorsController.get = function(req, res, next) { async.parallel({ 'not-found': async.apply(meta.errors.get, true), analytics: async.apply(analytics.getErrorAnalytics) }, function(err, data) { + if (err) { + return next(err); + } + res.render('admin/advanced/errors', data); }); }; -errorsController.export = function(req, res) { +errorsController.export = function(req, res, next) { async.waterfall([ async.apply(meta.errors.get, false), async.apply(json2csv) ], function(err, csv) { + if (err) { + return next(err); + } + res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="404.csv"').send(csv); }); }; diff --git a/src/controllers/admin/homepage.js b/src/controllers/admin/homepage.js index f503dd8865..89ef0db928 100644 --- a/src/controllers/admin/homepage.js +++ b/src/controllers/admin/homepage.js @@ -49,6 +49,10 @@ homePageController.get = function(req, res, next) { name: 'Popular' } ].concat(categoryData)}, function(err, data) { + if (err) { + return next(err); + } + data.routes.push({ route: '', name: 'Custom' diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js index bf0975058a..50c44ef1b7 100644 --- a/src/controllers/admin/settings.js +++ b/src/controllers/admin/settings.js @@ -25,13 +25,21 @@ function renderEmail(req, res, next) { var emailsPath = path.join(__dirname, '../../../public/templates/emails'); utils.walk(emailsPath, function(err, emails) { + if (err) { + return next(err); + } + async.map(emails, function(email, next) { var path = email.replace(emailsPath, '').substr(1).replace('.tpl', ''); fs.readFile(email, function(err, original) { + if (err) { + return next(err); + } + var text = meta.config['email:custom:' + path] ? meta.config['email:custom:' + path] : original.toString(); - next(err, { + next(null, { path: path, fullpath: email, text: text, @@ -39,6 +47,10 @@ function renderEmail(req, res, next) { }); }); }, function(err, emails) { + if (err) { + return next(err); + } + res.render('admin/settings/email', { emails: emails, sendable: emails.filter(function(email) { diff --git a/src/controllers/admin/sounds.js b/src/controllers/admin/sounds.js index 6e7ebf3f19..b583125c5a 100644 --- a/src/controllers/admin/sounds.js +++ b/src/controllers/admin/sounds.js @@ -6,6 +6,10 @@ var soundsController = {}; soundsController.get = function(req, res, next) { meta.sounds.getFiles(function(err, sounds) { + if (err) { + return next(err); + } + sounds = Object.keys(sounds).map(function(name) { return { name: name diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 90dae8a243..b507e092cf 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -61,6 +61,10 @@ uploadsController.uploadTouchIcon = function(req, res, next) { if (validateUpload(req, res, next, uploadedFile, allowedTypes)) { file.saveFileToLocal('touchicon-orig.png', 'system', uploadedFile.path, function(err, imageObj) { + if (err) { + return next(err); + } + // Resize the image into squares for use as touch icons at various DPIs async.each(sizes, function(size, next) { async.series([ diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index bc301b1873..98dc6c1fe4 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -96,6 +96,10 @@ function registerAndLoginUser(req, res, userData, callback) { userData: userData, interstitials: [] }, function(err, data) { + if (err) { + return next(err); + } + // If interstitials are found, save registration attempt into session and abort var deferRegistration = data.interstitials.length; @@ -144,6 +148,10 @@ authenticationController.registerComplete = function(req, res, next) { userData: req.session.registration, interstitials: [] }, function(err, data) { + if (err) { + return next(err); + } + var callbacks = data.interstitials.reduce(function(memo, cur) { if (cur.hasOwnProperty('callback') && typeof cur.callback === 'function') { memo.push(async.apply(cur.callback, req.session.registration, req.body)); @@ -243,6 +251,10 @@ function continueLogin(req, res, next) { winston.verbose('[auth] Triggering password reset for uid ' + userData.uid + ' due to password policy'); req.session.passwordExpired = true; user.reset.generate(userData.uid, function(err, code) { + if (err) { + return res.status(403).send(err.message); + } + res.status(200).send(nconf.get('relative_path') + '/reset/' + code); }); } else { diff --git a/src/controllers/index.js b/src/controllers/index.js index 4ad3d5ab0a..10076846df 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -202,6 +202,10 @@ Controllers.registerInterstitial = function(req, res, next) { userData: req.session.registration, interstitials: [] }, function(err, data) { + if (err) { + return next(err); + } + if (!data.interstitials.length) { return next(); } @@ -212,6 +216,10 @@ Controllers.registerInterstitial = function(req, res, next) { var errors = req.flash('error'); async.parallel(renders, function(err, sections) { + if (err) { + return next(err); + } + res.render('registerComplete', { errors: errors, sections: sections diff --git a/src/groups/cover.js b/src/groups/cover.js index 99f2791a2e..4dae0c2ba4 100644 --- a/src/groups/cover.js +++ b/src/groups/cover.js @@ -7,6 +7,7 @@ var fs = require('fs'); var crypto = require('crypto'); var Jimp = require('jimp'); var mime = require('mime'); +var winston = require('winston'); var db = require('../database'); var file = require('../file'); @@ -70,6 +71,10 @@ module.exports = function(Groups) { ], function (err) { if (err) { return fs.unlink(tempPath, function(unlinkErr) { + if (unlinkErr) { + winston.error(unlinkErr); + } + callback(err); // send back original error }); } diff --git a/src/install.js b/src/install.js index f9fae4ffdf..296e30fdc4 100644 --- a/src/install.js +++ b/src/install.js @@ -257,6 +257,9 @@ function createAdmin(callback) { type: 'string' }], success = function(err, results) { + if (err) { + return callback(err); + } if (!results) { return callback(new Error('aborted')); } diff --git a/src/languages.js b/src/languages.js index 2ad64fd588..70dbf4f4ae 100644 --- a/src/languages.js +++ b/src/languages.js @@ -30,6 +30,10 @@ Languages.get = function(code, key, callback) { var languageData; fs.readFile(path.join(__dirname, '../public/language/', code, key), { encoding: 'utf-8' }, function(err, data) { + if (err && err.code !== 'ENOENT') { + return callback(err); + } + // If language file in core cannot be read, then no language file present try { languageData = JSON.parse(data) || {}; diff --git a/src/meta/css.js b/src/meta/css.js index 478acab284..4834cedc95 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -170,6 +170,10 @@ module.exports = function(Meta) { winston.verbose('[meta/css] Reading stylesheet ' + filePath.split('/').pop() + ' from file'); fs.readFile(filePath, function(err, file) { + if (err) { + return callback(err); + } + Meta.css[filename] = file; callback(); }); diff --git a/src/meta/errors.js b/src/meta/errors.js index 4449a55f57..9c8d1ff772 100644 --- a/src/meta/errors.js +++ b/src/meta/errors.js @@ -20,6 +20,10 @@ module.exports = function(Meta) { Meta.errors.get = function(escape, callback) { db.getSortedSetRevRangeByScoreWithScores('errors:404', 0, -1, '+inf', '-inf', function(err, data) { + if (err) { + return callback(err); + } + data = data.map(function(nfObject) { nfObject.value = escape ? validator.escape(nfObject.value) : nfObject.value; return nfObject; diff --git a/src/meta/js.js b/src/meta/js.js index e25ac45ba0..b781a3b06f 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -267,6 +267,10 @@ module.exports = function(Meta) { } async.map(paths, fs.readFile, function(err, files) { + if (err) { + return callback(err); + } + Meta.js.target[target] = { cache: files[0], map: files[1] || '' diff --git a/src/meta/tags.js b/src/meta/tags.js index 50afe9a1db..4eaba839e6 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -89,6 +89,10 @@ module.exports = function(Meta) { plugins.fireHook('filter:meta.getLinkTags', defaultLinks, next); } }, function(err, results) { + if (err) { + return callback(err); + } + meta = results.tags.concat(meta || []).map(function(tag) { if (!tag || typeof tag.content !== 'string') { winston.warn('Invalid meta tag. ', tag); diff --git a/src/meta/themes.js b/src/meta/themes.js index ed6b061db9..dd93a206c2 100644 --- a/src/meta/themes.js +++ b/src/meta/themes.js @@ -56,6 +56,10 @@ module.exports = function(Meta) { }); }, function (err, themes) { + if (err) { + return callback(err); + } + themes = themes.filter(function (theme) { return (theme !== undefined); }); diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 62dcc97d4c..e5caf9ba00 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -326,6 +326,10 @@ middleware.processLanguages = function(req, res, next) { if (code && key) { languages.get(code, key[0], function(err, language) { + if (err) { + return next(err); + } + res.status(200).json(language); }); } else { diff --git a/src/plugins.js b/src/plugins.js index 6eba5de2ce..c531dfa27d 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -94,6 +94,10 @@ var middleware; function(next) { // Build language code list fs.readdir(path.join(__dirname, '../public/language'), function(err, directories) { + if (err) { + return next(err); + } + Plugins.languageCodes = directories.filter(function(code) { return code !== 'TODO'; }); @@ -206,7 +210,11 @@ var middleware; } }); } else { - winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.'); + if (err) { + winston.error(err); + } else { + winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.'); + } } next(false); @@ -392,7 +400,7 @@ var middleware; next(); }); }, function(err) { - next(null, plugins); + next(err, plugins); }); } ], callback); diff --git a/src/plugins/install.js b/src/plugins/install.js index 1ea826784c..3e3ee42ff3 100644 --- a/src/plugins/install.js +++ b/src/plugins/install.js @@ -96,6 +96,10 @@ module.exports = function(Plugins) { } Plugins.get(id, function(err, pluginData) { + if (err) { + return callback(err); + } + Plugins.fireHook('action:plugin.' + type, id); callback(null, pluginData); }); diff --git a/src/plugins/load.js b/src/plugins/load.js index 70aea70ccd..99019e371f 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -221,6 +221,10 @@ module.exports = function(Plugins) { fallbackMap = {}; utils.walk(pathToFolder, function(err, languages) { + if (err) { + return callback(err); + } + async.each(languages, function(pathToLang, next) { fs.readFile(pathToLang, function(err, file) { if (err) { diff --git a/src/posts/flags.js b/src/posts/flags.js index 9eae966cda..dcc2f32f1d 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -106,6 +106,10 @@ module.exports = function(Posts) { async.series([ function(next) { db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function(err, uids) { + if (err) { + return next(err); + } + async.each(uids, function(uid, next) { var nid = 'post_flag:' + pid + ':uid:' + uid; async.parallel([ diff --git a/src/posts/summary.js b/src/posts/summary.js index 885a0e444d..e3e948c292 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -101,6 +101,10 @@ module.exports = function(Posts) { next(null, post); }); }, function(err, posts) { + if (err) { + return callback(err); + } + plugins.fireHook('filter:post.getPostSummaryByPids', {posts: posts, uid: uid}, function(err, postData) { callback(err, postData.posts); }); diff --git a/src/reset.js b/src/reset.js index f1e27738e8..56669b6e2d 100644 --- a/src/reset.js +++ b/src/reset.js @@ -82,7 +82,12 @@ function resetTheme(themeId) { type: 'local', id: themeId }, function(err) { - winston.info('[reset] Theme reset to ' + themeId); + if (err) { + winston.warn('[reset] Failed to reset theme to ' + themeId); + } else { + winston.info('[reset] Theme reset to ' + themeId); + } + process.exit(); }); } diff --git a/src/rewards/admin.js b/src/rewards/admin.js index e6c32bc34b..0bde13ac00 100644 --- a/src/rewards/admin.js +++ b/src/rewards/admin.js @@ -46,6 +46,10 @@ rewards.save = function(data, callback) { } async.each(data, save, function(err) { + if (err) { + return callback(err); + } + saveConditions(data, callback); }); }; @@ -125,6 +129,10 @@ function getActiveRewards(callback) { } db.getSetMembers('rewards:list', function(err, rewards) { + if (err) { + return callback(err); + } + async.eachSeries(rewards, load, function(err) { callback(err, activeRewards); }); diff --git a/src/rewards/index.js b/src/rewards/index.js index c811ce933c..bbccab69f3 100644 --- a/src/rewards/index.js +++ b/src/rewards/index.js @@ -111,14 +111,22 @@ function getRewardsByRewardData(rewards, callback) { function checkCondition(reward, method, callback) { method(function(err, value) { + if (err) { + return callback(err); + } + plugins.fireHook('filter:rewards.checkConditional:' + reward.conditional, {left: value, right: reward.value}, function(err, bool) { - callback(bool); + callback(err || bool); }); }); } function giveRewards(uid, rewards, callback) { getRewardsByRewardData(rewards, function(err, rewardData) { + if (err) { + return callback(err); + } + async.each(rewards, function(reward, next) { plugins.fireHook('action:rewards.award:' + reward.rid, {uid: uid, reward: rewardData[rewards.indexOf(reward)]}); db.sortedSetIncrBy('uid:' + uid + ':rewards', 1, reward.id, next); diff --git a/src/routes/debug.js b/src/routes/debug.js index b81938ccc9..a24a159e18 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -2,6 +2,7 @@ var express = require('express'), nconf = require('nconf'), + winston = require('winston'), user = require('./../user'), categories = require('./../categories'), topics = require('./../topics'), @@ -16,6 +17,10 @@ module.exports = function(app, middleware, controllers) { } user.getUserData(req.params.uid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { @@ -28,6 +33,10 @@ module.exports = function(app, middleware, controllers) { router.get('/cid/:cid', function (req, res) { categories.getCategoryData(req.params.cid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { @@ -38,6 +47,10 @@ module.exports = function(app, middleware, controllers) { router.get('/tid/:tid', function (req, res) { topics.getTopicData(req.params.tid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { @@ -48,6 +61,10 @@ module.exports = function(app, middleware, controllers) { router.get('/pid/:pid', function (req, res) { posts.getPostData(req.params.pid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index 706604ec6e..46617c2889 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -172,7 +172,15 @@ SocketHelpers.rescindUpvoteNotification = function(pid, fromuid) { notifications.rescind(nid); posts.getPostField(pid, 'uid', function(err, uid) { + if (err) { + return winston.error(err); + } + user.notifications.getUnreadCount(uid, function(err, count) { + if (err) { + return winston.error(err); + } + websockets.in('uid_' + uid).emit('event:notifications.updateCount', count); }); }); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 44c17ea476..6bb23a0698 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -122,6 +122,10 @@ SocketUser.reset.commit = function(socket, data, callback) { var parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate(); user.getUserField(uid, 'username', function(err, username) { + if (err) { + return callback(err); + } + emailer.send('reset_notify', uid, { username: username, date: parsedDate, diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index cf886b1d48..e71de5f18c 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -38,6 +38,10 @@ module.exports = function(SocketUser) { type: type, picture: undefined }, function(err, returnData) { + if (err) { + return next(err); + } + next(null, returnData.picture || ''); }); break; diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index 52c9fcd801..48c0f0fa49 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -29,6 +29,10 @@ module.exports = function(SocketUser) { } user.isAdministrator(socket.uid, function(err, isAdmin) { + if (err) { + return callback(err); + } + if (!isAdmin && data.uid !== socket.uid) { return callback(new Error('[[error:no-privileges]]')); } diff --git a/src/upgrade.js b/src/upgrade.js index ae3877458d..317f1837da 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -46,6 +46,10 @@ Upgrade.upgrade = function(callback) { function(next) { // Prepare for upgrade & check to make sure the upgrade is possible db.get('schemaDate', function(err, value) { + if (err) { + return next(err); + } + if(!value) { db.set('schemaDate', latestSchema, function() { next(); @@ -506,8 +510,16 @@ Upgrade.upgrade = function(callback) { var privilegesAPI = require('./privileges'); db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + async.eachSeries(cids, function(cid, next) { privilegesAPI.categories.list(cid, function(err, data) { + if (err) { + return next(err); + } + var groups = data.groups; var users = data.users; @@ -628,6 +640,10 @@ Upgrade.upgrade = function(callback) { var meta = require('./meta'); db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + async.eachSeries(cids, function(cid, next) { privilegesAPI.categories.list(cid, function(err, data) { if (err) { @@ -694,8 +710,16 @@ Upgrade.upgrade = function(callback) { var privilegesAPI = require('./privileges'); db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + async.eachSeries(cids, function(cid, next) { privilegesAPI.categories.list(cid, function(err, data) { + if (err) { + return next(err); + } + var groups = data.groups; var users = data.users; diff --git a/src/user/auth.js b/src/user/auth.js index f2fa917962..b8ff96053b 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -101,7 +101,7 @@ module.exports = function(User) { async.each(expiredSids, function(sid, next) { User.auth.revokeSession(sid, uid, next); }, function(err) { - next(null, sessions); + next(err, sessions); }); } ], function (err, sessions) { diff --git a/src/user/digest.js b/src/user/digest.js index 81b6ea0220..f3978bd8e5 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -60,6 +60,10 @@ var utils = require('../../public/src/utils'); Digest.getSubscribers = function(interval, callback) { db.getSortedSetRange('digest:' + interval + ':uids', 0, -1, function(err, subscribers) { + if (err) { + return callback(err); + } + plugins.fireHook('filter:digest.subscribers', { interval: interval, subscribers: subscribers diff --git a/src/user/follow.js b/src/user/follow.js index 094d33c460..7295f01519 100644 --- a/src/user/follow.js +++ b/src/user/follow.js @@ -85,6 +85,10 @@ module.exports = function(User) { start: start, stop: stop }, function(err, data) { + if (err) { + return callback(err); + } + User.getUsers(data.uids, uid, callback); }); }); diff --git a/src/user/picture.js b/src/user/picture.js index 38d013cab0..78ad131ab4 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -200,6 +200,10 @@ module.exports = function(User) { ], function(err) { if (err) { return fs.unlink(data.file.path, function(unlinkErr) { + if (unlinkErr) { + winston.error(unlinkErr); + } + callback(err); // send back the original error }); } diff --git a/src/user/profile.js b/src/user/profile.js index 8b2a83f152..34fb24467c 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -49,6 +49,10 @@ module.exports = function(User) { } User.getUserField(uid, 'email', function(err, email) { + if (err) { + return next(err); + } + if(email === data.email) { return next(); } diff --git a/tests/categories.js b/tests/categories.js index c5bbee22cc..b607e6fc0a 100644 --- a/tests/categories.js +++ b/tests/categories.js @@ -25,6 +25,8 @@ describe('Categories', function() { blockclass: 'category-blue', order: '5' }, function(err, category) { + assert.equal(err, null); + categoryObj = category; done.apply(this, arguments); }); @@ -41,6 +43,8 @@ describe('Categories', function() { stop: -1, uid: 0 }, function(err, categoryData) { + assert.equal(err, null); + assert(categoryData); assert.equal(categoryObj.name, categoryData.name); assert.equal(categoryObj.description, categoryData.description); @@ -60,6 +64,8 @@ describe('Categories', function() { stop: 10, uid: 0 }, function(err, result) { + assert.equal(err, null); + assert(Array.isArray(result.topics)); assert(result.topics.every(function(topic) { return topic instanceof Object; @@ -79,6 +85,7 @@ describe('Categories', function() { uid: 0, targetUid: 1 }, function(err, result) { + assert.equal(err, null); assert(Array.isArray(result.topics)); assert(result.topics.every(function(topic) { return topic instanceof Object && topic.uid === '1'; diff --git a/tests/database/list.js b/tests/database/list.js index 137c465c2e..c008a0f784 100644 --- a/tests/database/list.js +++ b/tests/database/list.js @@ -125,6 +125,7 @@ describe('List methods', function() { assert.equal(arguments.length, 1); db.getListRange('testList5', 0, -1, function(err, list) { + assert.equal(err, null); assert.equal(Array.isArray(list), true); assert.equal(list.length, 2); assert.equal(list.indexOf('1'), -1); @@ -148,6 +149,7 @@ describe('List methods', function() { assert.equal(err, null); assert.equal(arguments.length, 1); db.getListRange('testList6', 0, -1, function(err, list) { + assert.equal(err, null); assert.equal(list.length, 3); assert.deepEqual(list, ['1', '2', '3']); done(); diff --git a/tests/groups.js b/tests/groups.js index 6c30bddd77..43b1857b99 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -270,6 +270,7 @@ describe('Groups', function() { if (err) return done(err); Groups.isMember(1, 'Test', function(err, isMember) { + assert.equal(err, null); assert.strictEqual(true, isMember); done(); @@ -284,6 +285,7 @@ describe('Groups', function() { if (err) return done(err); Groups.isMember(1, 'Test', function(err, isMember) { + assert.equal(err, null); assert.strictEqual(false, isMember); done(); diff --git a/tests/messaging.js b/tests/messaging.js index 3092e42b35..e7a936bae1 100644 --- a/tests/messaging.js +++ b/tests/messaging.js @@ -17,6 +17,10 @@ describe('Messaging Library', function() { async.apply(User.create, { username: 'baz', password: 'quux' }), // restricted user async.apply(User.create, { username: 'herp', password: 'derp' }) // regular user ], function(err, uids) { + if (err) { + return done(err); + } + testUids = uids; async.parallel([ async.apply(Groups.join, 'administrators', uids[0]), diff --git a/tests/mocks/databasemock.js b/tests/mocks/databasemock.js index a895d25c8a..b18d19e338 100644 --- a/tests/mocks/databasemock.js +++ b/tests/mocks/databasemock.js @@ -75,6 +75,10 @@ before(function(done) { db.init(function(err) { + if (err) { + return done(err); + } + //Clean up db.flushdb(function(err) { if(err) { diff --git a/tests/topics.js b/tests/topics.js index bf890f3ebe..575ce6a0b1 100644 --- a/tests/topics.js +++ b/tests/topics.js @@ -23,6 +23,10 @@ describe('Topic\'s', function() { }; User.create({username: userData.username, password: userData.password, email: userData.email}, function(err, uid) { + if (err) { + return done(err); + } + categories.create({ name: 'Test Category', description: 'Test category created by testing script', @@ -30,6 +34,10 @@ describe('Topic\'s', function() { blockclass: 'category-blue', order: '5' }, function(err, category) { + if (err) { + return done(err); + } + categoryObj = category; topic = { @@ -91,6 +99,10 @@ describe('Topic\'s', function() { before(function(done) { topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { + if (err) { + return done(err); + } + newTopic = result.topicData; newPost = result.postData; done(); @@ -134,6 +146,10 @@ describe('Topic\'s', function() { before(function(done) { topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { + if (err) { + return done(err); + } + newTopic = result.topicData; newPost = result.postData; done(); @@ -236,6 +252,10 @@ describe('Topic\'s', function() { async.waterfall([ function(done){ topics.post({uid: topic.userId, title: 'Topic to be ignored', content: 'Just ignore me, please!', cid: topic.categoryId}, function(err, result) { + if (err) { + return done(err); + } + newTopic = result.topicData; newTid = newTopic.tid; done(); diff --git a/tests/user.js b/tests/user.js index 30a835fe83..32f43c7dbf 100644 --- a/tests/user.js +++ b/tests/user.js @@ -28,6 +28,10 @@ describe('User', function() { description: 'A test', order: 1 }, function(err, categoryObj) { + if (err) { + return done(err); + } + testCid = categoryObj.cid; done(); }); @@ -67,6 +71,7 @@ describe('User', function() { describe('.isModerator()', function() { it('should return false', function(done) { User.isModerator(testUid, testCid, function(err, isModerator) { + assert.equal(err, null); assert.equal(isModerator, false); done(); }); @@ -74,6 +79,7 @@ describe('User', function() { it('should return two false results', function(done) { User.isModerator([testUid, testUid], testCid, function(err, isModerator) { + assert.equal(err, null); assert.equal(isModerator[0], false); assert.equal(isModerator[1], false); done(); @@ -82,6 +88,7 @@ describe('User', function() { it('should return two false results', function(done) { User.isModerator(testUid, [testCid, testCid], function(err, isModerator) { + assert.equal(err, null); assert.equal(isModerator[0], false); assert.equal(isModerator[1], false); done(); From d272a634925ce3f0a2be5fe2afe111f76717f28c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 17 Aug 2016 14:12:35 +0300 Subject: [PATCH 070/386] fix messaging.parse to be err first --- src/messaging.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/messaging.js b/src/messaging.js index 62dd569015..1ce4160d62 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -128,7 +128,10 @@ var async = require('async'), }); async.map(messages, function(message, next) { - Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function(result) { + Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function(err, result) { + if (err) { + return next(err); + } message.content = result; message.cleanedContent = S(result).stripTags().decodeHTMLEntities().s; next(null, message); @@ -195,7 +198,7 @@ var async = require('async'), Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) { plugins.fireHook('filter:parse.raw', message, function(err, parsed) { if (err) { - return callback(message); + return callback(err); } var messageData = { @@ -209,7 +212,7 @@ var async = require('async'), }; plugins.fireHook('filter:messaging.parse', messageData, function(err, messageData) { - callback(messageData.parsedMessage); + callback(err, messageData ? messageData.parsedMessage : ''); }); }); }; From 32dea338b00b752dda2adfeb34980efc905d4ede Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Wed, 17 Aug 2016 09:03:13 -0400 Subject: [PATCH 071/386] Latest translations and fallbacks --- public/language/ar/error.json | 2 ++ public/language/bg/error.json | 2 ++ public/language/bn/error.json | 2 ++ public/language/cs/error.json | 2 ++ public/language/da/error.json | 2 ++ public/language/de/error.json | 2 ++ public/language/el/error.json | 2 ++ public/language/en@pirate/error.json | 2 ++ public/language/en_US/error.json | 2 ++ public/language/es/error.json | 2 ++ public/language/es/modules.json | 2 +- public/language/et/error.json | 2 ++ public/language/fa_IR/error.json | 2 ++ public/language/fi/error.json | 2 ++ public/language/fr/error.json | 2 ++ public/language/gl/error.json | 2 ++ public/language/he/error.json | 2 ++ public/language/hu/error.json | 2 ++ public/language/id/error.json | 2 ++ public/language/it/error.json | 2 ++ public/language/ja/error.json | 2 ++ public/language/ko/error.json | 2 ++ public/language/lt/error.json | 2 ++ public/language/ms/error.json | 2 ++ public/language/nb/error.json | 2 ++ public/language/nl/error.json | 2 ++ public/language/pl/error.json | 2 ++ public/language/pt_BR/error.json | 2 ++ public/language/ro/error.json | 2 ++ public/language/ru/error.json | 2 ++ public/language/rw/error.json | 2 ++ public/language/sc/error.json | 2 ++ public/language/sk/error.json | 2 ++ public/language/sl/error.json | 2 ++ public/language/sr/error.json | 2 ++ public/language/sv/error.json | 2 ++ public/language/th/error.json | 2 ++ public/language/tr/error.json | 2 ++ public/language/vi/error.json | 2 ++ public/language/zh_CN/error.json | 2 ++ public/language/zh_TW/error.json | 2 ++ 41 files changed, 81 insertions(+), 1 deletion(-) diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 4717871d44..f1a81bf0c3 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 6a3ef24696..48d57a8dee 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Можете да изтривате публикациите си до %1 час(а) и %2 минута/и, след като ги пуснете", "post-delete-duration-expired-days": "Можете да изтривате публикациите си до %1 ден(а), след като ги пуснете", "post-delete-duration-expired-days-hours": "Можете да изтривате публикациите си до %1 ден(а) и %2 час(а), след като ги пуснете", + "cant-delete-topic-has-reply": "Не можете да изтриете темата си, след като в нея вече има един отговор", + "cant-delete-topic-has-replies": "Не можете да изтриете темата си, след като в нея вече има %1 отговора", "content-too-short": "Моля, въведете по-дълъг текст на публикацията. Публикациите трябва да съдържат поне %1 символ(а).", "content-too-long": "Моля, въведете по-кратък текст на публикацията. Публикациите трябва да съдържат не повече от %1 символ(а).", "title-too-short": "Моля, въведете по-дълго заглавие. Заглавията трябва да съдържат поне %1 символ(а).", diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 6a07599afa..66e8758fcb 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 7162bf5ebd..1c27c9e522 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/da/error.json b/public/language/da/error.json index 78e2b469c3..9be724b215 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Venligst indtast et længere indlæg. Indlægget skal mindst indeholde %1 karakter(er).", "content-too-long": "Venligt indtast et kortere indlæg. Indlæg kan ikke være længere end %1 karakter(er).", "title-too-short": "Venligst indtast en længere titel. Titlen skal mindst indeholde %1 karakter(er).", diff --git a/public/language/de/error.json b/public/language/de/error.json index 544aead1c3..d10bf13c3e 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Bitte schreibe einen längeren Beitrag. Beiträge sollten mindestens %1 Zeichen enthalten.", "content-too-long": "Bitte schreibe einen kürzeren Beitrag. Beiträge können nicht länger als %1 Zeichen sein.", "title-too-short": "Bitte gebe einen längeren Titel ein. Ein Titel muss mindestens %1 Zeichen enthalten.", diff --git a/public/language/el/error.json b/public/language/el/error.json index b7f9c621ed..32c609f78b 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 6a614b6de4..2ec9878ee8 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index 6a614b6de4..2ec9878ee8 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/es/error.json b/public/language/es/error.json index 0df59adbad..f49449598c 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "No puedes borrar mensajes hasta pasado %1 hora(s) y %2 minuto(s) después de haberlo escrito", "post-delete-duration-expired-days": "No puedes borrar mensajes hasta pasado %1 día(s) después de haberlo escrito", "post-delete-duration-expired-days-hours": "No puedes borrar mensajes hasta pasado %1 día(s) y %2 hora(s) después de haberlo escrito", + "cant-delete-topic-has-reply": "No puedes borrar tu tema después de que tenga respuestas", + "cant-delete-topic-has-replies": "No puedes borrar tu tema despues de que tenga ℅1 respuestas", "content-too-short": "Por favor introduzca una publicación más larga. Las publicaciones deben contener al menos %1 caractere(s).", "content-too-long": "Por favor introduzca un mensaje más corto. Los mensajes no pueden exceder los %1 caractere(s).", "title-too-short": "Por favor introduzca un título más largo. Los títulos deben contener al menos %1 caractere(s).", diff --git a/public/language/es/modules.json b/public/language/es/modules.json index d47569a921..b2ce72e4ae 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Foto", "composer.upload-picture": "Subir foto", "composer.upload-file": "Subir archivo", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Modo Zen", "bootbox.ok": "OK", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/et/error.json b/public/language/et/error.json index 48024ec16a..3aed5d7967 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Palun tehke pikem postitus. Postituse pikkus peab olema vähemalt %1 tähemärk(i).", "content-too-long": "Palun tehke lühem postitus. Postituse pikkus peab olema vähem kui %1 tähemärk(i).", "title-too-short": "Palun sisesta pikem pealkiri. Pealkirjad ei saa olla lühemad kui %1 tähemärk(i).", diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 965f4a00c2..7fbda8edfe 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "شما تنها می توانید %1 ساعت(ها) %2 دقیقه(ها) پس از فرستادن پست آن‌ را پاک کنید", "post-delete-duration-expired-days": "شما تنها می توانید %1 روز(ها) پس از فرستادن پست آن‌ را پاک کنید", "post-delete-duration-expired-days-hours": "شما تنها می توانید %1 روز(ها) %2 ساعت(ها) پس از فرستادن پست آن‌ را پاک کنید", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "خواهشمندیم پست بلندتری بنویسید. پست‌ها دست‌کم باید %1 کاراکتر داشته باشند.", "content-too-long": "لطفا طول مطلب را کوتاه تر کنید. طول پست نمیتواند بیشتر از %1 کاراکتر باشد.", "title-too-short": "لطفا یک عنوان بلندتر وارد کنید. عنوان باید حداقل %1 کاراکتر داشته باشد.", diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 8862df7356..536a3854a8 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Ole hyvä ja syötä pidempi viesti. Sen pitäisi sisältää ainakin %1 merkki(ä).", "content-too-long": "Ole hyvä ja syötä lyhyempi viesti. Sen voi sisältää vain %1 merkki(ä).", "title-too-short": "Ole hyä ja syötä pidempi otsikko. Sen pitäisi sisältää anakin %1 merkki(ä).", diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 0a494e47ab..f240a77009 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Vous ne pouvez supprimer un message que pendant %1 heure(s) et %2 minute(s) après l'avoir posté.", "post-delete-duration-expired-days": "Vous ne pouvez supprimer un message que pendant %1 jour(s) après l'avoir posté.", "post-delete-duration-expired-days-hours": "Vous ne pouvez supprimer un message que pendant %1 jour(s) et %2 heure(s) après l'avoir posté.", + "cant-delete-topic-has-reply": "Vous ne pouvez pas supprimer votre sujet s'il a au moins une réponse.", + "cant-delete-topic-has-replies": "Vous ne pouvez pas supprimer votre sujet s'il a au moins %1 réponses.", "content-too-short": "Veuillez entrer un message plus long. %1 caractère(s) minimum.", "content-too-long": "Veuillez poster un message plus cours. Les messages ne peuvent être plus long que %1 caractère(s).", "title-too-short": "Veuillez entrer un titre plus long. %1 caractère(s) minimum.", diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 76bdf60486..c13679179d 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Por favor, introduce unha publicación máis longa. Debe conter %1 carácter(es) como mínimo.", "content-too-long": "Por favor, introduce unha publicación máis curta. As publicacións non poden conter máis de %1 carácter(es).", "title-too-short": "Por favor, introduce un título máis longo. Os títulos deben conter %1 carácter(es) como mínimo.", diff --git a/public/language/he/error.json b/public/language/he/error.json index 7b00a4a288..52ee6b7549 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "אנא הכנס פוסט ארוך יותר. פוסטים חייבים להכיל לפחות %1 תווים.", "content-too-long": "אנא הכנס פוסט קצר יותר. פוסטים חייבים להיות קצרים יותר מ-%1 תווים.", "title-too-short": "אנא הכנס כותרת ארוכה יותר. כותרות חייבות להכיל לפחות %1 תווים.", diff --git a/public/language/hu/error.json b/public/language/hu/error.json index ce999c9ead..1718b8f115 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/id/error.json b/public/language/id/error.json index 822698d462..cac1d43532 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/it/error.json b/public/language/it/error.json index e2ff61bf88..1eff8f77b0 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Inserisci un testo più lungo. Il messaggio deve contenere almeno %1 caratteri.", "content-too-long": "Inserisci un post più breve. I post non possono essere più lunghi di %1 caratteri.", "title-too-short": "Inserisci un titolo più lungo. I titoli devono contenere almeno %1 caratteri.", diff --git a/public/language/ja/error.json b/public/language/ja/error.json index ba607d9c09..848d308257 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "より長く投稿を書いて下さい。投稿にはせめて%1文字が必要です。", "content-too-long": "より短く投稿を書いて下さい。投稿が%1文字以上が許されません。", "title-too-short": "より長くタイトルを書いて下さい。タイトルはせめて%1文字が必要です。", diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 1a0e568eb3..e4e3686ae8 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "게시물의 내용이 너무 짧습니다. 내용은 최소 %1자 이상이어야 합니다.", "content-too-long": "게시물의 내용이 너무 깁니다. 내용은 최대 %1자 이내로 작성할 수 있습니다.", "title-too-short": "제목이 너무 짧습니다. 제목은 최소 %1자 이상이어야 합니다.", diff --git a/public/language/lt/error.json b/public/language/lt/error.json index a1c40b2542..33a845a2f3 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Prašome parašyti ilgesni pranešimą. Pranešimas turi sudaryti mažiausiai %1 simboli(us)", "content-too-long": "Prašome parašyti trumpesnį pranešimą. Pranešimas negali sudaryti daugiau %1 simboli(us)", "title-too-short": "Prašome įvesti ilgesni pavadinimą. Pavadinimas turi sudaryti mažiausiai %1 simboli(us)", diff --git a/public/language/ms/error.json b/public/language/ms/error.json index d7b0fed05c..64e13cdc36 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Sila masukkan kiriman yang lebih panjang. Kiriman mesti mengandungi sekurang-kurangnya %1 aksara().", "content-too-long": "Sila masukkan kiriman yang lebih ringkas. Kiriman mesti mengandungi tidak lebih %1 aksara().", "title-too-short": "Sila masukkan tajuk yang lebih panjang. Tajuk mesti mengandungi sekurang-kurangnya %1 aksara().", diff --git a/public/language/nb/error.json b/public/language/nb/error.json index b2df20d45c..b9756632e9 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Vennligst skriv et lengre innlegg. Innlegg må inneholde minst %1 tegn.", "content-too-long": "Vennligst skriv et kortere innlegg. Innlegg kan ikke være lengre enn %1 tegn.", "title-too-short": "Vennligst skriv en lengre tittel. Titler må inneholde minst %1 tegn.", diff --git a/public/language/nl/error.json b/public/language/nl/error.json index c22b6ec22c..541de4e1ea 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Je kunt berichten pas %1 uur %2 minuten na het plaatsen verwijderen.", "post-delete-duration-expired-days": "Je kunt berichten pas %1 dagen na het plaatsen verwijderen.", "post-delete-duration-expired-days-hours": "Je kunt berichten pas %1 dag(en) %2 uur na het plaatsen verwijderen.", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Geef wat meer inhoud aan een bericht! Berichten dienen uit minimaal %1 teken(s) te bestaan.", "content-too-long": "Kort het bericht wat in, het aantal gebruikte tekens overschrijdt het ingestelde limiet want berichten mogen niet meer dan %1 teken(s) bevatten.", "title-too-short": "Geef een titel op die uit meer tekens bestaat. Titels dienen ten minste uit %1 teken(s) te bestaan.", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 0a887ccf89..c6618a2f59 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Możesz kasować posty przez %1 godzin(-y) i %2 minut(-y) po napisaniu", "post-delete-duration-expired-days": "Możesz kasować posty przez %1 dni po napisaniu", "post-delete-duration-expired-days-hours": "Możesz kasować posty przez %1 dni i %2 godzin(-y) po napisaniu", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Prosimy wpisać dłuższy post. Posty powinny zawierać co najmniej %1 znaków.", "content-too-long": "Prosimy wpisać krótszy post. Posty nie mogą zawierać więcej niż %1 znaków.", "title-too-short": "Prosimy podać dłuższy tytuł. Tytuły powinny zawierać co najmniej %1 znaków.", diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index 64286e8352..780e0b5ec0 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Você só pode deletar posts por %1 hora(s) e %2 minutos(s) depois de postar", "post-delete-duration-expired-days": "Você só pode deletar posts por %1 dia(s) depois de postar", "post-delete-duration-expired-days-hours": "Você só pode deletar posts por %1 dia(s) e %2 hora(s) depois de postar", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Por favor digite um post maior. Posts precisam conter ao menos %1 caractere(s).", "content-too-long": "Por favor digite um post mais curto. Posts não podem ser maiores que %1 caractere(s)", "title-too-short": "Por favor digite um título maior. Títulos devem conter no mínimo %1 caractere(s)", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index fdb92e303b..c0dc6a64a1 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/ru/error.json b/public/language/ru/error.json index a0e7ea3108..ba5f39bd70 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Слишком короткое сообщение. Минимум символов: %1.", "content-too-long": "Слишком длинное сообщение. Максимум символов: %1.", "title-too-short": "Слишком короткое сообщение. Минимум символов: %1.", diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 07e0a50743..3a24f2dd50 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Gerageza ushyireho ikintu kirekireho. Icyo ushyiraho kigomba kuba kigizwe nibura n'inyuguti (cyangwa ibimenyetso) zigera kuri %1.", "content-too-long": "Gerageza ushyireho ibintu bigufiyaho. Icyo ushyiraho kigomba kuba kigizwe n'inyuguti (cyangwa ibimenyetso) zirenga %1. ", "title-too-short": "Gerageza ushyireho umutwe muremureho. Umutwe ugomba kuba ugizwe n'inyuguti (cyangwa ibimenyetso) zigera kuri %1. ", diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 6a614b6de4..2ec9878ee8 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/sk/error.json b/public/language/sk/error.json index d5116a21d9..9788c91cbe 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/sl/error.json b/public/language/sl/error.json index 709870ec9c..d9a7b503af 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Prosimo napišite daljšo objavo. Objave morajo vsebovati vsaj %1 znak(ov).", "content-too-long": "Prosimo napišite krajšo objavo. Objave ne smejo vsebovati več kot %1 znak(ov).", "title-too-short": "Prosimo vnesite daljši naslov. Naslovi morajo vsebovati vsaj %1 znak(ov).", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 05241438be..d32aa4364b 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 172f78e6cd..aecff9870f 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Du kan endast radera inlägg inom %1 timmar(er) %2 minut(er) efter att ha skickat det", "post-delete-duration-expired-days": "Du kan endast radera inlägg inom %1 dag(ar) efter att ha skickat det", "post-delete-duration-expired-days-hours": "Du kan endast radera inlägg inom %1 dag(ar) %2 timm(ar) efter att ha skickat det", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Skriv ett längre inlägg. Inlägg måste innehålla minst %1 tecken.", "content-too-long": "Skriv ett kortare inlägg. Inlägg kan inte innehålla mer än %1 tecken.", "title-too-short": "Skriv en längre rubrik. Rubriker måste innehålla minst %1 tecken.", diff --git a/public/language/th/error.json b/public/language/th/error.json index 27373e3aa5..45d5070642 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 48811f7373..a5ffb946fe 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Gönderildikten %1 saat(s) %2 dakika(s) sonra iletini silmene izin verilir.", "post-delete-duration-expired-days": "Gönderildikten %1 gün(s) sonra iletini silmene izin verilir.", "post-delete-duration-expired-days-hours": "Gönderildikten %1 gün(s) %2 saat(s) sonra iletini silmene izin verilir.", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Lütfen daha uzun bir ileti girin. En az %1 karakter.", "content-too-long": "Lütfen daha kısa bir ileti girin. İletiler %1 karakterden uzun olamaz.", "title-too-short": "Lütfen daha uzun bir başlık girin. Başlıklar en az %1 karakter içermelidir.", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 6af283b8de..54f4e7d5c2 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giờ(s) 2 phút(s)", "post-delete-duration-expired-days": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s)", "post-delete-duration-expired-days-hours": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s) %2 giờ(s)", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Vui lòng nhập một bài viết dài hơn. Bài viết phải có tối thiểu %1 ký tự.", "content-too-long": "Vui lòng nhập một bài viết ngắn hơn. Bài viết chỉ có thể có tối đa %1 ký tự.", "title-too-short": "Vui lòng nhập tiêu đề dài hơn. Tiêu đề phải có tối thiểu %1 ký tự.", diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index aa496cca6f..5788c57c59 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "您只能在发表 %1 小时 %2 分钟后删除帖子", "post-delete-duration-expired-days": "您只能在发表 %1 天后删除帖子", "post-delete-duration-expired-days-hours": "您只能在发表 %1 天 %2 小时后删除帖子", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "请增添发帖内容,不能少于 %1 个字符。", "content-too-long": "请删减发帖内容,不能超过 %1 个字符。", "title-too-short": "请增加标题,不能少于 %1 个字符。", diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index 3c9a9849e6..3beacd4374 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "請輸入一個長一點的張貼內容。張貼內容長度不能少於 %1 字元。", "content-too-long": "請輸入一個短一點的張貼內容。張貼內容長度不能超過 %1 字元。", "title-too-short": "請輸入一個長一點的標題。標題長度不能少於 %1 字元。", From 59bd1d71f4a152d66164775f3cb3f40f0300f597 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 17 Aug 2016 16:32:53 -0400 Subject: [PATCH 072/386] return roomId in optional callback when opening new chat --- public/src/app.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/src/app.js b/public/src/app.js index a23125d5c4..66257e7980 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -287,7 +287,8 @@ app.cacheBuster = null; }); }; - app.newChat = function (touid) { + app.newChat = function (touid, callback) { + callback = callback || function() {}; if (!app.user.uid) { return app.alertError('[[error:not-logged-in]]'); } @@ -296,11 +297,14 @@ app.cacheBuster = null; if (err) { return app.alertError(err.message); } + if (!ajaxify.currentPage.startsWith('chats')) { app.openChat(roomId); } else { ajaxify.go('chats/' + roomId); } + + callback(false, roomId); }); }; From a36af97c0c6f4753c276bdeeeddb2e3d9613421a Mon Sep 17 00:00:00 2001 From: Anil Mandepudi Date: Wed, 17 Aug 2016 16:51:58 -0700 Subject: [PATCH 073/386] fix breadcrumb for account info page (#4958) --- src/controllers/accounts/info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/accounts/info.js b/src/controllers/accounts/info.js index 1117cb28c1..70ebf52081 100644 --- a/src/controllers/accounts/info.js +++ b/src/controllers/accounts/info.js @@ -30,7 +30,7 @@ infoController.get = function(req, res, next) { }, data.fields); userData.title = '[[pages:account/info]]'; - userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:settings]]'}]); + userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:account_info]]'}]); res.render('account/info', userData); }); From 0b431ec41c3f4ca8e9ba82fb5c74497d19dda2ea Mon Sep 17 00:00:00 2001 From: pichalite Date: Thu, 18 Aug 2016 00:15:36 +0000 Subject: [PATCH 074/386] move login sessions section to account info route --- src/controllers/accounts/info.js | 6 ++++-- src/controllers/accounts/settings.js | 8 +------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/controllers/accounts/info.js b/src/controllers/accounts/info.js index 1117cb28c1..11069194ea 100644 --- a/src/controllers/accounts/info.js +++ b/src/controllers/accounts/info.js @@ -18,7 +18,8 @@ infoController.get = function(req, res, next) { async.parallel({ ips: async.apply(user.getIPs, res.locals.uid, 4), history: async.apply(user.getModerationHistory, res.locals.uid), - fields: async.apply(user.getUserFields, res.locals.uid, ['banned']) + fields: async.apply(user.getUserFields, res.locals.uid, ['banned']), + sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID) }, function(err, data) { if (err) { return next(err); @@ -28,7 +29,8 @@ infoController.get = function(req, res, next) { ips: data.ips, history: data.history }, data.fields); - + + userData.sessions = data.sessions; userData.title = '[[pages:account/info]]'; userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:settings]]'}]); diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index 9efa8ebe6b..3b169bf83c 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -37,19 +37,13 @@ settingsController.get = function(req, res, callback) { }, homePageRoutes: function(next) { getHomePageRoutes(next); - }, - ips: function (next) { - user.getIPs(userData.uid, 4, next); - }, - sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID) + } }, next); }, function(results, next) { userData.settings = results.settings; userData.languages = results.languages; userData.homePageRoutes = results.homePageRoutes; - userData.ips = results.ips; - userData.sessions = results.sessions; plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next); }, function(data, next) { From 096cbf30cf5fc3186ad3c172630d8ed0edfcfd43 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 18 Aug 2016 13:42:38 +0300 Subject: [PATCH 075/386] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 346176c76e..ae345d7125 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.20", - "nodebb-theme-vanilla": "5.1.9", + "nodebb-theme-persona": "4.1.21", + "nodebb-theme-vanilla": "5.1.10", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From bf3b270c77d58f1f945bfcebe94ba56446e1d440 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Thu, 18 Aug 2016 09:02:32 -0400 Subject: [PATCH 076/386] Latest translations and fallbacks --- public/language/it/category.json | 8 ++++---- public/language/it/error.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/language/it/category.json b/public/language/it/category.json index 27c6ff7e94..bb6aef06dd 100644 --- a/public/language/it/category.json +++ b/public/language/it/category.json @@ -10,10 +10,10 @@ "share_this_category": "Condividi questa Categoria", "watch": "Osserva", "ignore": "Ignora", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", + "watching": "Seguito", + "ignoring": "Ignorato", + "watching.description": "Mostra discussione in non letti", + "ignoring.description": "Non mostrare discussione in non letti", "watch.message": "Non stai seguendo gli aggiornamenti di questa categoria", "ignore.message": "Da ora saranno ignorati gli aggiornamenti di questa categoria", "watched-categories": "Categorie osservate" diff --git a/public/language/it/error.json b/public/language/it/error.json index 1eff8f77b0..23b0710512 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -14,13 +14,13 @@ "invalid-password": "Password non valida", "invalid-username-or-password": "Si prega di specificare sia un nome utente che una password", "invalid-search-term": "Termine di ricerca non valido", - "csrf-invalid": "We were unable to log you in, likely due to an expired session. Please try again", - "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2", + "csrf-invalid": "Non siamo riusciti a farti connettere, probabilmente perché la sessione è scaduta. Per favore riprova.", + "invalid-pagination-value": "Valore di paginazione non valido, deve essere almeno %1 ed al massimo %2", "username-taken": "Nome utente già preso", "email-taken": "Email già esistente", "email-not-confirmed": "La tua Email deve essere ancora confermata, per favore clicca qui per confermare la tua Email.", "email-not-confirmed-chat": "Non potrai chattare finchè non avrai confermato la tua email, per favore clicca qui per farlo ora.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "La tua email non è ancora stata confermata, per favore controlla la tua casella di posta elettronica per l'email di conferma.", "no-email-to-confirm": "Questo forum richiede la conferma dell'indirizzo email, per favore clicca qui per inserirne uno", "email-confirm-failed": "Non possiamo confermare la tua email, per favore prova ancora più tardi.", "confirm-email-already-sent": "Email di conferma già inviata, per favore attendere %1 minuti per richiederne un'altra.", From 093b21fc310bf2b435ea56892d08afa1f2f0cb64 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Aug 2016 10:05:52 -0400 Subject: [PATCH 077/386] fixes #4954 --- public/language/en_GB/global.json | 4 +++- src/controllers/index.js | 31 ++++++++++++++++++++++++++++++- src/routes/index.js | 1 + src/views/400.tpl | 4 ++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/views/400.tpl diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index 6641c28261..b8cadf0bc6 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -7,8 +7,10 @@ "403.login": "Perhaps you should try logging in?", "404.title": "Not Found", "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.", - "500.title": "Internal error.", + "500.title": "Internal Error.", "500.message": "Oops! Looks like something went wrong!", + "400.title": "Bad Request.", + "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.", "register": "Register", "login": "Login", diff --git a/src/controllers/index.js b/src/controllers/index.js index 10076846df..253c5ffe43 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -381,6 +381,35 @@ Controllers.handle404 = function(req, res) { } }; +Controllers.handleURIErrors = function(err, req, res, next) { + // Handle cases where malformed URIs are passed in + if (err instanceof URIError) { + var tidMatch = req.path.match(/^\/topic\/(\d+)\//); + var cidMatch = req.path.match(/^\/category\/(\d+)\//); + + if (tidMatch) { + res.redirect(nconf.get('relative_path') + tidMatch[0]); + } else if (cidMatch) { + res.redirect(nconf.get('relative_path') + cidMatch[0]); + } else { + winston.warn('[controller] Bad request: ' + req.path); + if (res.locals.isAPI) { + res.status(400).json({ + error: '[[global:400.title]]' + }); + } else { + req.app.locals.middleware.buildHeader(req, res, function() { + res.render('400', { error: validator.escape(String(err.message)) }); + }); + } + } + + return; + } else { + next(); + } +}; + Controllers.handleErrors = function(err, req, res, next) { switch (err.code) { case 'EBADCSRFTOKEN': @@ -403,7 +432,7 @@ Controllers.handleErrors = function(err, req, res, next) { res.json({path: validator.escape(path), error: err.message}); } else { req.app.locals.middleware.buildHeader(req, res, function() { - res.render('500', {path: validator.escape(path), error: validator.escape(String(err.message))}); + res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) }); }); } }; diff --git a/src/routes/index.js b/src/routes/index.js index 1e51f5d59e..3415aaf145 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -153,6 +153,7 @@ module.exports = function(app, middleware, hotswapIds) { })); app.use('/vendor/jquery/timeago/locales', middleware.processTimeagoLocales); app.use(controllers.handle404); + app.use(controllers.handleURIErrors); app.use(controllers.handleErrors); // Add plugin routes diff --git a/src/views/400.tpl b/src/views/400.tpl new file mode 100644 index 0000000000..9c263fcff1 --- /dev/null +++ b/src/views/400.tpl @@ -0,0 +1,4 @@ +
    + [[global:400.title]] +

    [[global:400.message, {config.relative_path}]]

    +
    From 48ade6099c3d01e55e0f22d41ed8eb7db609c9be Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Aug 2016 11:07:44 -0400 Subject: [PATCH 078/386] fixes #4469 --- src/notifications.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/notifications.js b/src/notifications.js index 1d466f374b..86a13e3224 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -495,6 +495,8 @@ var utils = require('../public/src/utils'); } else if (numUsers > 2) { notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]'; } + + notifications[modifyIndex].path = set[set.length-1].path; break; case 'new_register': From a412ef04b0ed45f7b6d542f64853d8fff109c384 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Aug 2016 13:29:32 -0400 Subject: [PATCH 079/386] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae345d7125..3feb54729c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.21", + "nodebb-theme-persona": "4.1.22", "nodebb-theme-vanilla": "5.1.10", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", From 8d745ceb074e8f81834bc5008e1f3bf60d047836 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 18 Aug 2016 14:39:51 -0400 Subject: [PATCH 080/386] moving chat dropdown generation to tpl /cc @pichalite --- package.json | 4 +-- public/less/generics.less | 6 +++++ public/src/modules/chat.js | 52 ++++++++++---------------------------- 3 files changed, 22 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 3feb54729c..4102742aab 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.22", - "nodebb-theme-vanilla": "5.1.10", + "nodebb-theme-persona": "4.1.23", + "nodebb-theme-vanilla": "5.1.11", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", diff --git a/public/less/generics.less b/public/less/generics.less index bca1b45649..d2be83d01e 100644 --- a/public/less/generics.less +++ b/public/less/generics.less @@ -94,6 +94,12 @@ .user-icon-style(24px, 1.5rem); } + &.avatar-md { + width: 32px; + height: 32px; + .user-icon-style(32px, 1.5rem); + } + &.avatar-lg { width: 128px; height: 128px; diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index f2ff94bd55..445b4212e4 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -26,6 +26,15 @@ define('chat', [ module.loadChatsDropdown(chatsListEl); }); + chatsListEl.on('click', '[data-roomid]', function() { + var roomId = this.getAttribute('data-roomid'); + if (!ajaxify.currentPage.match(/^chats\//)) { + app.openChat(roomId); + } else { + ajaxify.go('chats/' + roomId); + } + }); + $('[component="chats/mark-all-read"]').on('click', function() { socket.emit('modules.chats.markAllRead', function(err) { if (err) { @@ -106,44 +115,11 @@ define('chat', [ chatsListEl.empty(); - if (!rooms.length) { - translator.translate('[[modules:chat.no_active]]', function(str) { - $('
  • ') - .addClass('no_active') - .html('' + str + '') - .appendTo(chatsListEl); - }); - return; - } - - rooms.forEach(function(roomObj) { - function createUserImage(userObj) { - return '' + - (userObj.picture ? - '' : - '
    ' + userObj['icon:text'] + '
    ') + - ' ' + - roomObj.usernames + '
    '; - } - - var dropdownEl = $('
  • ') - .attr('data-roomId', roomObj.roomId) - .appendTo(chatsListEl); - - if (roomObj.lastUser) { - dropdownEl.append(createUserImage(roomObj.lastUser)); - } else { - translator.translate('[[modules:chat.no-users-in-room]]', function(str) { - dropdownEl.append(str); - }); - } - - dropdownEl.click(function() { - if (!ajaxify.currentPage.match(/^chats\//)) { - app.openChat(roomObj.roomId); - } else { - ajaxify.go('chats/' + roomObj.roomId); - } + templates.parse('partials/chat_dropdown', { + rooms: rooms + }, function(html) { + translator.translate(html, function(translated) { + chatsListEl.html(translated); }); }); }); From d5b8b1da56fabe3edd3c7bb7f2de8ffbaa929af9 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Fri, 19 Aug 2016 09:03:20 -0400 Subject: [PATCH 081/386] Latest translations and fallbacks --- public/language/ar/global.json | 3 ++- public/language/bg/global.json | 3 ++- public/language/bn/global.json | 3 ++- public/language/cs/global.json | 3 ++- public/language/da/global.json | 3 ++- public/language/de/global.json | 3 ++- public/language/el/global.json | 3 ++- public/language/en@pirate/global.json | 3 ++- public/language/en_US/global.json | 3 ++- public/language/es/global.json | 3 ++- public/language/et/global.json | 3 ++- public/language/fa_IR/global.json | 3 ++- public/language/fi/global.json | 3 ++- public/language/fr/global.json | 3 ++- public/language/gl/global.json | 3 ++- public/language/he/global.json | 3 ++- public/language/hu/global.json | 3 ++- public/language/id/global.json | 3 ++- public/language/it/global.json | 3 ++- public/language/ja/global.json | 3 ++- public/language/ko/global.json | 3 ++- public/language/lt/global.json | 3 ++- public/language/ms/global.json | 3 ++- public/language/nb/global.json | 3 ++- public/language/nl/global.json | 3 ++- public/language/pl/global.json | 3 ++- public/language/pt_BR/global.json | 3 ++- public/language/ro/global.json | 3 ++- public/language/ru/global.json | 3 ++- public/language/rw/global.json | 3 ++- public/language/sc/global.json | 3 ++- public/language/sk/global.json | 3 ++- public/language/sl/global.json | 3 ++- public/language/sr/global.json | 3 ++- public/language/sv/global.json | 3 ++- public/language/th/global.json | 3 ++- public/language/tr/error.json | 4 ++-- public/language/tr/global.json | 3 ++- public/language/tr/login.json | 2 +- public/language/vi/global.json | 3 ++- public/language/zh_CN/global.json | 3 ++- public/language/zh_TW/global.json | 3 ++- 42 files changed, 83 insertions(+), 43 deletions(-) diff --git a/public/language/ar/global.json b/public/language/ar/global.json index 75516c6d09..96356a3bce 100644 --- a/public/language/ar/global.json +++ b/public/language/ar/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/bg/global.json b/public/language/bg/global.json index 8993ea3df3..189c8d3990 100644 --- a/public/language/bg/global.json +++ b/public/language/bg/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Въведете номер на страница", "upload_file": "Качване на файл", "upload": "Качване", - "allowed-file-types": "Разрешените файлови типове са: %1" + "allowed-file-types": "Разрешените файлови типове са: %1", + "unsaved-changes": "Имате незапазени промени. Наистина ли искате да напуснете тази страница?" } \ No newline at end of file diff --git a/public/language/bn/global.json b/public/language/bn/global.json index abc0e8cda4..bfde1d9003 100644 --- a/public/language/bn/global.json +++ b/public/language/bn/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/cs/global.json b/public/language/cs/global.json index 83860b52cc..d78d3797f4 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Zadejte číslo stránky", "upload_file": "Nahrár soubor", "upload": "Nahrát", - "allowed-file-types": "Povolené typy souborů jsou %1" + "allowed-file-types": "Povolené typy souborů jsou %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/da/global.json b/public/language/da/global.json index b918c4d51c..2eda261502 100644 --- a/public/language/da/global.json +++ b/public/language/da/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Indsæt sideantal", "upload_file": "Upload fil", "upload": "Upload", - "allowed-file-types": "Tilladte filtyper er %1" + "allowed-file-types": "Tilladte filtyper er %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/de/global.json b/public/language/de/global.json index cddeca336f..496c212943 100644 --- a/public/language/de/global.json +++ b/public/language/de/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Seitennummer eingeben", "upload_file": "Datei hochladen", "upload": "Hochladen", - "allowed-file-types": "Erlaubte Dateitypen sind %1" + "allowed-file-types": "Erlaubte Dateitypen sind %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/el/global.json b/public/language/el/global.json index e8b8255a1a..32eacb6466 100644 --- a/public/language/el/global.json +++ b/public/language/el/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/en@pirate/global.json b/public/language/en@pirate/global.json index 1b78cd651b..6c86c89999 100644 --- a/public/language/en@pirate/global.json +++ b/public/language/en@pirate/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/en_US/global.json b/public/language/en_US/global.json index 802a81d4bb..e7f511d90a 100644 --- a/public/language/en_US/global.json +++ b/public/language/en_US/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/es/global.json b/public/language/es/global.json index 79fd9c1a88..f16dc7e12e 100644 --- a/public/language/es/global.json +++ b/public/language/es/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Escribe el número de página", "upload_file": "Subir archivo", "upload": "Subir", - "allowed-file-types": "Los tipos de archivos permitidos son: %1" + "allowed-file-types": "Los tipos de archivos permitidos son: %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/et/global.json b/public/language/et/global.json index 0735277f4d..3add6a941c 100644 --- a/public/language/et/global.json +++ b/public/language/et/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Sisesta lehekülje number", "upload_file": "Lae fail üles", "upload": "Lae üles", - "allowed-file-types": "Lubatud faili formaadid on %1" + "allowed-file-types": "Lubatud faili formaadid on %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/fa_IR/global.json b/public/language/fa_IR/global.json index 8888c6bc17..ce48647e40 100644 --- a/public/language/fa_IR/global.json +++ b/public/language/fa_IR/global.json @@ -92,5 +92,6 @@ "enter_page_number": "شماره صفحه را وارد کنید", "upload_file": "بارگذاری فایل", "upload": "بارگذاری", - "allowed-file-types": "فایل قابل قبول اینها هستند %1" + "allowed-file-types": "فایل قابل قبول اینها هستند %1", + "unsaved-changes": "تغییرات شما ذخیره نشده. شما مطمئن هستید که میخواهید از اینجا دور شوید؟" } \ No newline at end of file diff --git a/public/language/fi/global.json b/public/language/fi/global.json index 1a9176b1be..8488b07bfe 100644 --- a/public/language/fi/global.json +++ b/public/language/fi/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/fr/global.json b/public/language/fr/global.json index 36209bd2fb..10b70a2148 100644 --- a/public/language/fr/global.json +++ b/public/language/fr/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Entrer un numéro de page", "upload_file": "Envoyer un fichier", "upload": "Envoyer", - "allowed-file-types": "Les types de fichiers autorisés sont : %1" + "allowed-file-types": "Les types de fichiers autorisés sont : %1", + "unsaved-changes": "Vous avez des modifications non sauvegardées. Êtes-vous sûr de vouloir naviguer tout de même ?" } \ No newline at end of file diff --git a/public/language/gl/global.json b/public/language/gl/global.json index 29b4471221..6f0aafd671 100644 --- a/public/language/gl/global.json +++ b/public/language/gl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Escribe o número da páxina", "upload_file": "Subir arquivo ", "upload": "Subir", - "allowed-file-types": "Os tipos de arquivos permitidos son: %1" + "allowed-file-types": "Os tipos de arquivos permitidos son: %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/he/global.json b/public/language/he/global.json index 2d1d55a783..6e76c19ea3 100644 --- a/public/language/he/global.json +++ b/public/language/he/global.json @@ -92,5 +92,6 @@ "enter_page_number": "הכנס מספר עמוד", "upload_file": "העלה קובץ", "upload": "העלה", - "allowed-file-types": "פורמטי הקבצים המורשים הם %1" + "allowed-file-types": "פורמטי הקבצים המורשים הם %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/hu/global.json b/public/language/hu/global.json index 116730ac61..b7f0edef28 100644 --- a/public/language/hu/global.json +++ b/public/language/hu/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/id/global.json b/public/language/id/global.json index 8ef7a1d220..aac14e74d7 100644 --- a/public/language/id/global.json +++ b/public/language/id/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/it/global.json b/public/language/it/global.json index e63dceae07..b20daeef18 100644 --- a/public/language/it/global.json +++ b/public/language/it/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ja/global.json b/public/language/ja/global.json index 2d2ce15b92..1e89ac1a32 100644 --- a/public/language/ja/global.json +++ b/public/language/ja/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ko/global.json b/public/language/ko/global.json index b8c38121d1..e404ac910a 100644 --- a/public/language/ko/global.json +++ b/public/language/ko/global.json @@ -92,5 +92,6 @@ "enter_page_number": "페이지 번호를 입력하세요", "upload_file": "파일 업로드", "upload": "업로드", - "allowed-file-types": "사용가능한 파일 유형: %1" + "allowed-file-types": "사용가능한 파일 유형: %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/lt/global.json b/public/language/lt/global.json index 3368f99fe2..3892b4ede8 100644 --- a/public/language/lt/global.json +++ b/public/language/lt/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ms/global.json b/public/language/ms/global.json index 20bfd41623..e226d5f93c 100644 --- a/public/language/ms/global.json +++ b/public/language/ms/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/nb/global.json b/public/language/nb/global.json index 9f24bcd29e..1e8af5dc12 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/nl/global.json b/public/language/nl/global.json index 95569088e6..c2cf89003a 100644 --- a/public/language/nl/global.json +++ b/public/language/nl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Voer paginanummer in", "upload_file": "Upload bestand", "upload": "Upload", - "allowed-file-types": "Toegestane bestandstypen zijn %1" + "allowed-file-types": "Toegestane bestandstypen zijn %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/pl/global.json b/public/language/pl/global.json index 946993295c..f3a25ee6b2 100644 --- a/public/language/pl/global.json +++ b/public/language/pl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Wpisz numer strony", "upload_file": "Załaduj plik", "upload": "Załaduj", - "allowed-file-types": "Dozwolone typy plików %1" + "allowed-file-types": "Dozwolone typy plików %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json index 115d36e0b3..69c907a516 100644 --- a/public/language/pt_BR/global.json +++ b/public/language/pt_BR/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Digite o número da página", "upload_file": "Fazer upload de arquivo", "upload": "Upload", - "allowed-file-types": "Os tipos de arquivo permitidos são %1" + "allowed-file-types": "Os tipos de arquivo permitidos são %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ro/global.json b/public/language/ro/global.json index 3b8a7bed12..7d2f15e71c 100644 --- a/public/language/ro/global.json +++ b/public/language/ro/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Introdu numărul paginei", "upload_file": "Încărcați fișierul", "upload": "Încărcați", - "allowed-file-types": "Tipuri de fișiere permise sunt %1" + "allowed-file-types": "Tipuri de fișiere permise sunt %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ru/global.json b/public/language/ru/global.json index 291cc69983..d5ed6dc2b9 100644 --- a/public/language/ru/global.json +++ b/public/language/ru/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Введите номер страницы", "upload_file": "Загрузить файл", "upload": "Загрузить", - "allowed-file-types": "Разрешенные форматы файлов %1" + "allowed-file-types": "Разрешенные форматы файлов %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/rw/global.json b/public/language/rw/global.json index cc17af95c4..1f82d7cdfd 100644 --- a/public/language/rw/global.json +++ b/public/language/rw/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Shyiramo nimero ya paji", "upload_file": "Pakira ifayilo", "upload": "Pakira", - "allowed-file-types": "Ubwoko bw'amafayilo bwemewe ni %1" + "allowed-file-types": "Ubwoko bw'amafayilo bwemewe ni %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sc/global.json b/public/language/sc/global.json index 61ba71763c..9f7f952155 100644 --- a/public/language/sc/global.json +++ b/public/language/sc/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sk/global.json b/public/language/sk/global.json index 5772ec7ef2..14cc91fb07 100644 --- a/public/language/sk/global.json +++ b/public/language/sk/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sl/global.json b/public/language/sl/global.json index 5dd5474601..911fb6a7ff 100644 --- a/public/language/sl/global.json +++ b/public/language/sl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sr/global.json b/public/language/sr/global.json index 556cdb029f..97e71581ea 100644 --- a/public/language/sr/global.json +++ b/public/language/sr/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sv/global.json b/public/language/sv/global.json index c175e75cd7..5c1b98522e 100644 --- a/public/language/sv/global.json +++ b/public/language/sv/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Skriv in sidnummer", "upload_file": "Ladda upp en fil", "upload": "Ladda upp", - "allowed-file-types": "Tillåtna filtyper är %1" + "allowed-file-types": "Tillåtna filtyper är %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/th/global.json b/public/language/th/global.json index b280237529..eb81fb4ba5 100644 --- a/public/language/th/global.json +++ b/public/language/th/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index a5ffb946fe..5e8619faf2 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -55,8 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Gönderildikten %1 saat(s) %2 dakika(s) sonra iletini silmene izin verilir.", "post-delete-duration-expired-days": "Gönderildikten %1 gün(s) sonra iletini silmene izin verilir.", "post-delete-duration-expired-days-hours": "Gönderildikten %1 gün(s) %2 saat(s) sonra iletini silmene izin verilir.", - "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", - "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", + "cant-delete-topic-has-reply": "Bir ileti varken başlığı silemezsiniz", + "cant-delete-topic-has-replies": "Başlığına %1 ileti girildikten sonra silemezsin", "content-too-short": "Lütfen daha uzun bir ileti girin. En az %1 karakter.", "content-too-long": "Lütfen daha kısa bir ileti girin. İletiler %1 karakterden uzun olamaz.", "title-too-short": "Lütfen daha uzun bir başlık girin. Başlıklar en az %1 karakter içermelidir.", diff --git a/public/language/tr/global.json b/public/language/tr/global.json index 0e5c44add6..9e3c328dae 100644 --- a/public/language/tr/global.json +++ b/public/language/tr/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Sayfa numarasını girin", "upload_file": "Dosya yükle", "upload": "Yükle", - "allowed-file-types": "İzin verilen dosya tipleri %1" + "allowed-file-types": "İzin verilen dosya tipleri %1", + "unsaved-changes": "Kaydedilmemiş değişiklikler var. Çıkmak istediğinize emin misiniz?" } \ No newline at end of file diff --git a/public/language/tr/login.json b/public/language/tr/login.json index 9bf22ba985..5db8629859 100644 --- a/public/language/tr/login.json +++ b/public/language/tr/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Giriş Başarısız", "login_successful": "Başarıyla giriş yaptınız!", "dont_have_account": "Hesabınız yok mu?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Hareketsizlik nedeniyle yönetici panelinden çıkış yapıldı" } \ No newline at end of file diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 6928ecfefa..2cbb1536c8 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Nhập vào số trang", "upload_file": "Tải file lên", "upload": "Tải lên", - "allowed-file-types": "Các định dạng file được cho phép là %1" + "allowed-file-types": "Các định dạng file được cho phép là %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json index ca3c5d43f2..4be989cb9f 100644 --- a/public/language/zh_CN/global.json +++ b/public/language/zh_CN/global.json @@ -92,5 +92,6 @@ "enter_page_number": "输入页码", "upload_file": "上传文件", "upload": "上传", - "allowed-file-types": "允许的文件类型有 %1" + "allowed-file-types": "允许的文件类型有 %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/zh_TW/global.json b/public/language/zh_TW/global.json index 4533cbfc62..d530c0ace1 100644 --- a/public/language/zh_TW/global.json +++ b/public/language/zh_TW/global.json @@ -92,5 +92,6 @@ "enter_page_number": "輸入頁碼", "upload_file": "上傳檔案", "upload": "上傳", - "allowed-file-types": "允許的檔案類型是 %1" + "allowed-file-types": "允許的檔案類型是 %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file From 409621c661b0db2e41c0f4607b9d82b8ddc524a4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 19 Aug 2016 17:50:03 +0300 Subject: [PATCH 082/386] closes #4843 --- src/controllers/index.js | 4 ++ src/middleware/maintenance.js | 76 ++++++++++++++++++----------------- src/routes/index.js | 3 ++ 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index 253c5ffe43..47047924a4 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -339,6 +339,10 @@ Controllers.termsOfUse = function(req, res, next) { res.render('tos', {termsOfUse: meta.config.termsOfUse}); }; +Controllers.ping = function(req, res) { + res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); +}; + Controllers.handle404 = function(req, res) { var relativePath = nconf.get('relative_path'); var isLanguage = new RegExp('^' + relativePath + '/language/.*/.*.json'); diff --git a/src/middleware/maintenance.js b/src/middleware/maintenance.js index f03a804bb9..33079b88ef 100644 --- a/src/middleware/maintenance.js +++ b/src/middleware/maintenance.js @@ -3,8 +3,6 @@ var nconf = require('nconf'); var meta = require('../meta'); var user = require('../user'); -var translator = require('../../public/src/modules/translator'); - module.exports = function(middleware) { @@ -15,42 +13,46 @@ module.exports = function(middleware) { var url = req.url.replace(nconf.get('relative_path'), ''); var allowedRoutes = [ - '^/login', - '^/stylesheet.css', - '^/favicon', - '^/nodebb.min.js', - '^/vendor/fontawesome/fonts/fontawesome-webfont.woff', - '^/src/(modules|client)/[\\w/]+.js', - '^/templates/[\\w/]+.tpl', - '^/api/login', - '^/api/widgets/render', - '^/language/.+', - '^/uploads/system/site-logo.png' - ], - render = function() { - res.status(503); - var data = { - site_title: meta.config.title || 'NodeBB', - message: meta.config.maintenanceModeMessage - }; - if (!isApiRoute.test(url)) { - middleware.buildHeader(req, res, function() { - res.render('503', data); - }); - } else { - res.json(data); + '^/ping', + '^/sping', + '^/login', + '^/stylesheet.css', + '^/favicon', + '^/nodebb.min.js', + '^/vendor/fontawesome/fonts/fontawesome-webfont.woff', + '^/src/(modules|client)/[\\w/]+.js', + '^/templates/[\\w/]+.tpl', + '^/api/login', + '^/api/widgets/render', + '^/language/.+', + '^/uploads/system/site-logo.png' + ]; + var render = function() { + res.status(503); + var data = { + site_title: meta.config.title || 'NodeBB', + message: meta.config.maintenanceModeMessage + }; + if (!isApiRoute.test(url)) { + middleware.buildHeader(req, res, function() { + res.render('503', data); + }); + } else { + res.json(data); + } + }; + + var isAllowed = function(url) { + for(var x=0,numAllowed=allowedRoutes.length,route;x Date: Fri, 19 Aug 2016 12:40:52 -0400 Subject: [PATCH 083/386] saving getUsersCSV to event log and outputting to log if pressed --- src/controllers/admin/users.js | 7 +++++++ src/user/admin.js | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index e682045fce..bbfa71df95 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -5,6 +5,7 @@ var user = require('../../user'); var meta = require('../../meta'); var db = require('../../database'); var pagination = require('../../pagination'); +var events = require('../../events'); var usersController = {}; @@ -180,6 +181,12 @@ function render(req, res, data) { } usersController.getCSV = function(req, res, next) { + events.log({ + type: 'getUsersCSV', + uid: req.user.uid, + ip: req.ip + }); + user.getUsersCSV(function(err, data) { if (err) { return next(err); diff --git a/src/user/admin.js b/src/user/admin.js index e4384782ee..dd3134e442 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -5,6 +5,7 @@ var async = require('async'); var db = require('../database'); var posts = require('../posts'); var plugins = require('../plugins'); +var winston = require('winston'); module.exports = function(User) { @@ -27,6 +28,7 @@ module.exports = function(User) { }; User.getUsersCSV = function(callback) { + winston.info('[user/getUsersCSV] Compiling User CSV data'); var csvContent = ''; async.waterfall([ From f4fafeb500edea61054ae9eab481fa7529a3129c Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Sun, 21 Aug 2016 09:02:34 -0400 Subject: [PATCH 084/386] Latest translations and fallbacks --- public/language/fa_IR/error.json | 4 ++-- public/language/gl/category.json | 2 +- public/language/gl/error.json | 32 ++++++++++++++++---------------- public/language/gl/global.json | 2 +- public/language/gl/login.json | 2 +- public/language/gl/modules.json | 2 +- public/language/gl/topic.json | 14 +++++++------- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 7fbda8edfe..6063b7cfd7 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -55,8 +55,8 @@ "post-delete-duration-expired-hours-minutes": "شما تنها می توانید %1 ساعت(ها) %2 دقیقه(ها) پس از فرستادن پست آن‌ را پاک کنید", "post-delete-duration-expired-days": "شما تنها می توانید %1 روز(ها) پس از فرستادن پست آن‌ را پاک کنید", "post-delete-duration-expired-days-hours": "شما تنها می توانید %1 روز(ها) %2 ساعت(ها) پس از فرستادن پست آن‌ را پاک کنید", - "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", - "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", + "cant-delete-topic-has-reply": "اگر کسی به موضوع شما پاسخ داده باشد، نمیتوانید آنرا حذف نمائید", + "cant-delete-topic-has-replies": "اگر 1% به موضوع جواب داده شده باشد ، نمیتوانید آنرا حذف نمائید", "content-too-short": "خواهشمندیم پست بلندتری بنویسید. پست‌ها دست‌کم باید %1 کاراکتر داشته باشند.", "content-too-long": "لطفا طول مطلب را کوتاه تر کنید. طول پست نمیتواند بیشتر از %1 کاراکتر باشد.", "title-too-short": "لطفا یک عنوان بلندتر وارد کنید. عنوان باید حداقل %1 کاراکتر داشته باشد.", diff --git a/public/language/gl/category.json b/public/language/gl/category.json index 1ff9bc2daf..eb12b9660e 100644 --- a/public/language/gl/category.json +++ b/public/language/gl/category.json @@ -14,7 +14,7 @@ "ignoring": "Ignorando", "watching.description": "Amosa-los temas en \"non lidos\"", "ignoring.description": "Non amosa-los temas en \"non lidos\"", - "watch.message": "Agora vixías as novidades desta categoría", + "watch.message": "Agora Sigues as novidades desta categoría", "ignore.message": "Agora ignoras as novidades nesta categoría", "watched-categories": "Categorías vixiadas" } \ No newline at end of file diff --git a/public/language/gl/error.json b/public/language/gl/error.json index c13679179d..d7cf7fa7da 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -20,7 +20,7 @@ "email-taken": "Correo en uso", "email-not-confirmed": "O teu correo aínda non está confirmado, por favor pica aquí para confirmalo.", "email-not-confirmed-chat": "Non podes charlar ata que confirmes o teu correo, por favor pica aquí para confirmalo.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "O teu correo electrónico está sen confirmar. Por favor, busca o correo de confirmación na túa bandexa de entrada.", "no-email-to-confirm": "Este foro require confirmación de correo, por favor pica aquí para introducir un correo.", "email-confirm-failed": "Non podemos confirmar o teu correo, por favor téntao de novo máis tarde.", "confirm-email-already-sent": "O correo de confirmación foi enviado, agarda %1 minute(s) para enviar outro.", @@ -48,15 +48,15 @@ "post-edit-duration-expired-hours-minutes": "Só podes editar as publicacións %1 hora(s) %2 segundo(s) despois de envialas. ", "post-edit-duration-expired-days": "Só podes editar as publicacións %1 día(s) despois de envialas. ", "post-edit-duration-expired-days-hours": "Só podes editar as publicacións %1 día(s) %2 hora(s) despois de envialas. ", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", - "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", - "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", + "post-delete-duration-expired": "Só podes borrar mensaxes %1 segundo(s) despois de escribilos.", + "post-delete-duration-expired-minutes": "Só podes borrar mensaxes %1 minuto(s) despois de escribilos.", + "post-delete-duration-expired-minutes-seconds": "Só podes borrar mensaxes %1 minuto(s) e 2% segundo(s) despois de escribilos.", + "post-delete-duration-expired-hours": "Só podes borrar mensaxes %1 hora(s) despois de escribilos.", + "post-delete-duration-expired-hours-minutes": "Só podes borrar mensaxes %1 hora(s) e %2 minuto(s) despois de escribilos.", + "post-delete-duration-expired-days": "Só podes borrar mensaxes %1 día(s) despois de escribilos.", + "post-delete-duration-expired-days-hours": "Só podes borrar mensaxes %1 día(s) e %2 hora(s) despois de escribilos.", + "cant-delete-topic-has-reply": "Non podes borrar o teu tema cando xa ten respostas", + "cant-delete-topic-has-replies": "Non podes borrar o teu tema cando xa ten %1 respostas", "content-too-short": "Por favor, introduce unha publicación máis longa. Debe conter %1 carácter(es) como mínimo.", "content-too-long": "Por favor, introduce unha publicación máis curta. As publicacións non poden conter máis de %1 carácter(es).", "title-too-short": "Por favor, introduce un título máis longo. Os títulos deben conter %1 carácter(es) como mínimo.", @@ -74,12 +74,12 @@ "already-unfavourited": "Xa desgardaras esta publicación.", "cant-ban-other-admins": "Non podes botar outros administradores!", "cant-remove-last-admin": "Eres o único administrador. Engade outros administradores antes de quitarte a ti mesmo como administrador.", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "Retirar privilexios de administrador desta conta antes de intentar borrala", "invalid-image-type": "Tipo de imaxe inválida. Tipos admitidos: %1", "invalid-image-extension": "Extensión de imaxe inválida", "invalid-file-type": "Tipo de arquivo inválido. Tipos admitidos: %1", "group-name-too-short": "Nome de grupo moi curto", - "group-name-too-long": "Group name too long", + "group-name-too-long": "Nome de grupo demasiado longo", "group-already-exists": "O grupo xa existe", "group-name-change-not-allowed": "Cambio de nome do grupo non permitido", "group-already-member": "Xa eres parte deste grupo", @@ -118,10 +118,10 @@ "wrong-login-type-email": "Por favor, emprega o teu correo para contectarte", "wrong-login-type-username": "Por favor, usa o teu nome de usuario para conectarte", "invite-maximum-met": "Convidaches á cantidade máxima de persoas (%1 de %2).", - "no-session-found": "Non se atopa ningún inicio de sesión!", - "not-in-room": "O usuario non se encontra nesta sala", + "no-session-found": "Non se atopou ningún inicio de sesión!", + "not-in-room": "O usuario non se atopa nesta sala", "no-users-in-room": "Non hai usuarios nesta sala", "cant-kick-self": "Non te podes expulsar a ti mesmo do grupo", - "no-users-selected": "No user(s) selected", - "invalid-home-page-route": "Invalid home page route" + "no-users-selected": "Ningún usuario seleccionado", + "invalid-home-page-route": "Ruta de páxina de inicio inválida" } \ No newline at end of file diff --git a/public/language/gl/global.json b/public/language/gl/global.json index 6f0aafd671..8d1301e078 100644 --- a/public/language/gl/global.json +++ b/public/language/gl/global.json @@ -93,5 +93,5 @@ "upload_file": "Subir arquivo ", "upload": "Subir", "allowed-file-types": "Os tipos de arquivos permitidos son: %1", - "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" + "unsaved-changes": "Non gardaches tódolos cambios. Queres continuar e saír da páxina?" } \ No newline at end of file diff --git a/public/language/gl/login.json b/public/language/gl/login.json index 12530c7699..8b885cbb78 100644 --- a/public/language/gl/login.json +++ b/public/language/gl/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Erro ao iniciar sesión", "login_successful": "Sesión iniciada con éxito!", "dont_have_account": "Aínda non tes conta?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Debido a inactividade fuches desconectado do Panel de Control de Administradores" } \ No newline at end of file diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index 9bebb20db3..3cff4867cd 100644 --- a/public/language/gl/modules.json +++ b/public/language/gl/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Foto", "composer.upload-picture": "Subir foto", "composer.upload-file": "Subir arquivo", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Modo Zen", "bootbox.ok": "De acordo", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json index ab5fe853c3..3097bfec5e 100644 --- a/public/language/gl/topic.json +++ b/public/language/gl/topic.json @@ -26,14 +26,14 @@ "tools": "Ferramentas", "flag": "Reportar", "locked": "Pechado", - "pinned": "Pinned", - "moved": "Moved", + "pinned": "Fixo", + "moved": "Movido", "bookmark_instructions": "Pica aquí para volver á última mensaxe lida neste tema ", "flag_title": "Reportar esta mensaxe", "flag_success": "Esta mensaxe foi reportada para moderación.", "deleted_message": "Este tema foi borrado. Só os usuarios con privilexios administrativos poden velo.", "following_topic.message": "Agora recibirás notificacións cando alguén publique neste tema.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Poderás ver este tema na lista de No Lidos, pero non recibirás notificacións cando alguén escriba nel.", "ignoring_topic.message": "Xa non verás este fío na lista de fíos non lidos. Serás notificado cando sexas mencionado ou a túa publicación sexa votada.", "login_to_subscribe": "Por favor, identifícate para subscribirte a este tema.", "markAsUnreadForAll.success": "Publicación marcada como non lida para todos.", @@ -42,10 +42,10 @@ "watch": "Vixiar", "unwatch": "Deixar de vixiar", "watch.title": "Serás notificado canto haxa novas respostas neste tema", - "unwatch.title": "Deixar de vixiar este tema", + "unwatch.title": "Deixar de seguir este tema", "share_this_post": "Compartir esta publicación", - "watching": "Vendo", - "not-watching": "Non Vendo", + "watching": "Seguindo", + "not-watching": "Non seguindo", "ignoring": "Ignorar", "watching.description": "Notificádeme das novas repostas.
    Amosa-lo fío nos non lidos.", "not-watching.description": "Non me notifiquedes as novas respostas.
    Amosa-lo fío en non lidos se a categoría non está ignorada.", @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Este tema será movido á categoría", "fork_topic_instruction": "Fai clic nas publicacións que queiras dividir", "fork_no_pids": "Non seleccionaches ninguna publicación!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 mensaxe(s) seleccionada(s)", "fork_success": "Creouse un novo tema a partir do orixinal! Fai clic aquí para ir ó novo tema.", "delete_posts_instruction": "Fai clic nas mensaxes que queres eliminar/limpar", "composer.title_placeholder": "Introduce o título do teu tema", From e4fa0289d6b493c111936605e9793031c775940e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 22 Aug 2016 14:38:04 +0300 Subject: [PATCH 085/386] closes #4961 --- public/src/admin/advanced/events.js | 24 +++--------------------- src/controllers/admin/events.js | 24 ++++++++++++++++++++++-- src/events.js | 15 +++++++++------ src/socket.io/admin.js | 14 -------------- src/socket.io/user/ban.js | 15 ++++++++++++++- src/views/admin/advanced/events.tpl | 1 + 6 files changed, 49 insertions(+), 44 deletions(-) diff --git a/public/src/admin/advanced/events.js b/public/src/admin/advanced/events.js index 9075947d60..a952666786 100644 --- a/public/src/admin/advanced/events.js +++ b/public/src/admin/advanced/events.js @@ -1,8 +1,9 @@ "use strict"; -/* global define, socket, app, templates */ + +/* global define, socket, app */ -define('admin/advanced/events', ['forum/infinitescroll'], function(infinitescroll) { +define('admin/advanced/events', function() { var Events = {}; Events.init = function() { @@ -16,25 +17,6 @@ define('admin/advanced/events', ['forum/infinitescroll'], function(infinitescrol }); }); - infinitescroll.init(function(direction) { - if (direction < 0 || !$('.events').length) { - return; - } - - infinitescroll.loadMore('admin.getMoreEvents', $('[data-next]').attr('data-next'), function(data, done) { - if (data.events && data.events.length) { - templates.parse('admin/advanced/events', 'events', {events: data.events}, function(html) { - $('.events-list').append(html); - done(); - }); - - $('[data-next]').attr('data-next', data.next); - } else { - done(); - } - }); - }); - }; return Events; diff --git a/src/controllers/admin/events.js b/src/controllers/admin/events.js index ceee1e2a70..0e431d3d4e 100644 --- a/src/controllers/admin/events.js +++ b/src/controllers/admin/events.js @@ -1,18 +1,38 @@ 'use strict'; +var async = require('async'); + +var db = require('../../database'); var events = require('../../events'); +var pagination = require('../../pagination'); var eventsController = {}; eventsController.get = function(req, res, next) { - events.getEvents(0, 19, function(err, events) { + + var page = parseInt(req.query.page, 10) || 1; + var itemsPerPage = 20; + var start = (page - 1) * 20; + var stop = start + itemsPerPage - 1; + + async.parallel({ + eventCount: function(next) { + db.sortedSetCard('events:time', next); + }, + events: function(next) { + events.getEvents(start, stop, next); + } + }, function(err, results) { if (err) { return next(err); } + var pageCount = Math.max(1, Math.ceil(results.eventCount / itemsPerPage)); + res.render('admin/advanced/events', { - events: events, + events: results.events, + pagination: pagination.create(page, pageCount), next: 20 }); }); diff --git a/src/events.js b/src/events.js index 968e1b2f39..9ef6a05385 100644 --- a/src/events.js +++ b/src/events.js @@ -47,16 +47,19 @@ var async = require('async'), db.getObjects(keys, next); }, function(eventsData, next) { - eventsData.forEach(function(event) { - var e = utils.merge(event); - e.eid = e.uid = e.type = e.ip = undefined; - event.jsonString = JSON.stringify(e, null, 4); - event.timestampISO = new Date(parseInt(event.timestamp, 10)).toUTCString(); - }); addUserData(eventsData, 'uid', 'user', next); }, function(eventsData, next) { addUserData(eventsData, 'targetUid', 'targetUser', next); + }, + function(eventsData, next) { + eventsData.forEach(function(event) { + var e = utils.merge(event); + e.eid = e.uid = e.type = e.ip = e.user = undefined; + event.jsonString = JSON.stringify(e, null, 4); + event.timestampISO = new Date(parseInt(event.timestamp, 10)).toUTCString(); + }); + next(null, eventsData); } ], callback); }; diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 556eee0290..c599abf491 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -251,20 +251,6 @@ SocketAdmin.errors.clear = function(socket, data, callback) { meta.errors.clear(callback); }; -SocketAdmin.getMoreEvents = function(socket, next, callback) { - var start = parseInt(next, 10); - if (start < 0) { - return callback(null, {data: [], next: next}); - } - var stop = start + 10; - events.getEvents(start, stop, function(err, events) { - if (err) { - return callback(err); - } - callback(null, {events: events, next: stop + 1}); - }); -}; - SocketAdmin.deleteAllEvents = function(socket, data, callback) { events.deleteAll(callback); }; diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index a2c4ea50d3..41c60411e0 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -34,7 +34,20 @@ module.exports = function(SocketUser) { }; SocketUser.unbanUsers = function(socket, uids, callback) { - toggleBan(socket.uid, uids, user.unban, callback); + toggleBan(socket.uid, uids, user.unban, function(err) { + if (err) { + return callback(err); + } + + async.each(uids, function(uid, next) { + events.log({ + type: 'user-unban', + uid: socket.uid, + targetUid: uid, + ip: socket.ip + }, next); + }, callback); + }); }; function toggleBan(uid, uids, method, callback) { diff --git a/src/views/admin/advanced/events.tpl b/src/views/admin/advanced/events.tpl index f69d7928a5..f1c0973501 100644 --- a/src/views/admin/advanced/events.tpl +++ b/src/views/admin/advanced/events.tpl @@ -23,6 +23,7 @@
    {events.jsonString}
  • + From 3864e8044e392cd701a99a1ea2fe1e5372adf4e9 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Mon, 22 Aug 2016 09:02:56 -0400 Subject: [PATCH 086/386] Latest translations and fallbacks --- public/language/es/email.json | 2 +- public/language/es/global.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/es/email.json b/public/language/es/email.json index ddc0a47556..6bb5dd06f8 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -24,7 +24,7 @@ "digest.day": "día", "digest.week": "semana", "digest.month": "mes", - "digest.subject": "Resumen de 1%", + "digest.subject": "Resumen de %1", "notif.chat.subject": "Nuevo mensaje de chat recibido de %1", "notif.chat.cta": "Haz click aquí para continuar la conversación", "notif.chat.unsub.info": "Esta notificación de chat se te envió debido a tus ajustes de suscripción.", diff --git a/public/language/es/global.json b/public/language/es/global.json index f16dc7e12e..3cf4afbbae 100644 --- a/public/language/es/global.json +++ b/public/language/es/global.json @@ -93,5 +93,5 @@ "upload_file": "Subir archivo", "upload": "Subir", "allowed-file-types": "Los tipos de archivos permitidos son: %1", - "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" + "unsaved-changes": "Tienes cambios sin guardar. Seguro que quieres salir?" } \ No newline at end of file From de49de3c568a49fbe740277ae3c3908d4d316c76 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 22 Aug 2016 14:42:49 -0500 Subject: [PATCH 087/386] Show accounts using the same IP address as a registration queue entry (#4965) --- src/user/approval.js | 56 ++++++++++++++++--------- src/views/admin/manage/registration.tpl | 9 ++++ 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/user/approval.js b/src/user/approval.js index 8ccd5192aa..95b2daa68b 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -166,26 +166,44 @@ module.exports = function(User) { // temporary: see http://www.stopforumspam.com/forum/viewtopic.php?id=6392 user.ip = user.ip.replace('::ffff:', ''); - request({ - method: 'get', - url: 'http://api.stopforumspam.org/api' + - '?ip=' + encodeURIComponent(user.ip) + - '&email=' + encodeURIComponent(user.email) + - '&username=' + encodeURIComponent(user.username) + - '&f=json', - json: true - }, function (err, response, body) { - if (err) { - return next(null, user); - } - if (response.statusCode === 200 && body) { - user.spamData = body; - user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true; - user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true; - user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true; - } + async.parallel([ + function(next) { + User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1, function(err, uids) { + if (err) { + return next(err); + } - next(null, user); + User.getUsersFields(uids, ['uid', 'username', 'picture'], function(err, ipMatch) { + user.ipMatch = ipMatch; + next(err); + }); + }); + }, + function(next) { + request({ + method: 'get', + url: 'http://api.stopforumspam.org/api' + + '?ip=' + encodeURIComponent(user.ip) + + '&email=' + encodeURIComponent(user.email) + + '&username=' + encodeURIComponent(user.username) + + '&f=json', + json: true + }, function (err, response, body) { + if (err) { + return next(); + } + if (response.statusCode === 200 && body) { + user.spamData = body; + user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true; + user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true; + user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true; + } + + next(); + }); + } + ], function(err) { + next(err, user); }); }, next); } diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl index f751eca74d..5944219794 100644 --- a/src/views/admin/manage/registration.tpl +++ b/src/views/admin/manage/registration.tpl @@ -42,6 +42,15 @@ {users.ip} + +
    + + + +
    {users.ipMatch.icon:text}
    + + {users.ipMatch.username} + From 60ea7d51212f0fbdfca5bd583d47271f7fd746fd Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Aug 2016 16:24:28 -0400 Subject: [PATCH 088/386] fixes #4966 --- app.js | 2 ++ public/src/ajaxify.js | 4 +--- public/src/utils.js | 8 ++++++++ src/controllers/authentication.js | 11 ++++++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 2158d61282..74beb77d6e 100644 --- a/app.js +++ b/app.js @@ -103,6 +103,8 @@ function loadConfig() { nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path'))); nconf.set('core_templates_path', path.join(__dirname, 'src/views')); nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates')); + + nconf.set('url_parsed', url.parse(nconf.get('url'))); } diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index de20693eb0..d0592d8511 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -334,9 +334,7 @@ $(document).ready(function() { return; } - var internalLink = this.host === '' || // Relative paths are always internal links - (this.host === window.location.host && this.protocol === window.location.protocol && // Otherwise need to check if protocol and host match - (RELATIVE_PATH.length > 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true)); // Subfolder installs need this additional check + var internalLink = utils.isInternalURI(this, window.location, RELATIVE_PATH); if ($(this).attr('data-ajaxify') === 'false') { if (!internalLink) { diff --git a/public/src/utils.js b/public/src/utils.js index 5d44bcd4fa..190d0ab3c6 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -431,6 +431,14 @@ } return utils.props(obj[prop], newProps, value); + }, + + isInternalURI: function(targetLocation, referenceLocation, relative_path) { + return targetLocation.host === '' || // Relative paths are always internal links + ( + targetLocation.host === referenceLocation.host && targetLocation.protocol === referenceLocation.protocol && // Otherwise need to check if protocol and host match + (relative_path.length > 0 ? targetLocation.pathname.indexOf(relative_path) === 0 : true) // Subfolder installs need this additional check + ); } }; diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 98dc6c1fe4..0f74827be2 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -6,6 +6,7 @@ var passport = require('passport'); var nconf = require('nconf'); var validator = require('validator'); var _ = require('underscore'); +var url = require('url'); var db = require('../database'); var meta = require('../meta'); @@ -168,7 +169,7 @@ authenticationController.registerComplete = function(req, res, next) { } else { res.redirect(nconf.get('relative_path') + '/'); } - } + }; async.parallel(callbacks, function(err) { if (err) { @@ -187,7 +188,7 @@ authenticationController.registerComplete = function(req, res, next) { }); }; -authenticationController.registerAbort = function(req, res, next) { +authenticationController.registerAbort = function(req, res) { // End the session and redirect to home req.session.destroy(function() { res.redirect(nconf.get('relative_path') + '/'); @@ -197,7 +198,11 @@ authenticationController.registerAbort = function(req, res, next) { authenticationController.login = function(req, res, next) { // Handle returnTo data if (req.body.hasOwnProperty('returnTo') && !req.session.returnTo) { - req.session.returnTo = req.body.returnTo; + // As req.body is data obtained via userland, it is untrusted, restrict to internal links only + var parsed = url.parse(req.body.returnTo); + var isInternal = utils.isInternalURI(url.parse(req.body.returnTo), nconf.get('url_parsed'), nconf.get('relative_path')); + + req.session.returnTo = isInternal ? req.body.returnTo : nconf.get('url'); } if (plugins.hasListeners('action:auth.overrideLogin')) { From ebb50160cb9234a7c7b714dccbe0acf4462a2a64 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Aug 2016 18:16:09 -0400 Subject: [PATCH 089/386] wrap saving of url_parsed in conditional, since url isn't set during setup --- app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 74beb77d6e..7fdee397f9 100644 --- a/app.js +++ b/app.js @@ -104,7 +104,9 @@ function loadConfig() { nconf.set('core_templates_path', path.join(__dirname, 'src/views')); nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates')); - nconf.set('url_parsed', url.parse(nconf.get('url'))); + if (nconf.get('url')) { + nconf.set('url_parsed', url.parse(nconf.get('url'))); + } } From 862fd1a94a7aac4254f9f846ad2c380c662ce715 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Mon, 22 Aug 2016 17:16:52 -0500 Subject: [PATCH 090/386] ignore/watch categories recursively --- public/language/en_GB/category.json | 4 +-- src/socket.io/categories.js | 55 ++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/public/language/en_GB/category.json b/public/language/en_GB/category.json index d4da76f356..d581c8277d 100644 --- a/public/language/en_GB/category.json +++ b/public/language/en_GB/category.json @@ -18,8 +18,8 @@ "watching.description": "Show topics in unread", "ignoring.description": "Do not show topics in unread", - "watch.message": "You are now watching updates from this category", - "ignore.message": "You are now ignoring updates from this category", + "watch.message": "You are now watching updates from this category and all subcategories", + "ignore.message": "You are now ignoring updates from this category and all subcategories", "watched-categories": "Watched categories" } diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 545938d718..1c13adebea 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -1,6 +1,6 @@ 'use strict'; -var async = require('async'); +var async = require('async'); var db = require('../database'); var categories = require('../categories'); var privileges = require('../privileges'); @@ -171,23 +171,52 @@ SocketCategories.getMoveCategories = function(socket, data, callback) { }; SocketCategories.watch = function(socket, cid, callback) { - user.watchCategory(socket.uid, cid, function(err) { - if (err) { - return callback(err); - } - topics.pushUnreadCount(socket.uid, callback); - }); + ignoreOrWatch(user.watchCategory, socket, cid, callback); }; SocketCategories.ignore = function(socket, cid, callback) { - user.ignoreCategory(socket.uid, cid, function(err) { - if (err) { - return callback(err); - } - topics.pushUnreadCount(socket.uid, callback); - }); + ignoreOrWatch(user.ignoreCategory, socket, cid, callback); }; +function ignoreOrWatch(fn, socket, cid, callback) { + async.waterfall([ + function(next) { + db.getSortedSetRange('categories:cid', 0, -1, next); + }, + function(cids, next) { + categories.getCategoriesFields(cids, ['cid', 'parentCid'], next); + }, + function(categoryData, next) { + categoryData.forEach(function(c) { + c.cid = parseInt(c.cid, 10); + c.parentCid = parseInt(c.parentCid, 10); + }); + + var cids = [parseInt(cid, 10)]; + + // filter to subcategories of cid + + var any = true; + while (any) { + any = false; + categoryData.forEach(function(c) { + if (cids.indexOf(c.cid) === -1 && cids.indexOf(c.parentCid) !== -1) { + cids.push(c.cid); + any = true; + } + }); + } + + async.each(cids, function(cid, next) { + fn(socket.uid, cid, next); + }, next); + }, + function(next) { + topics.pushUnreadCount(socket.uid, next); + } + ], callback); +} + SocketCategories.isModerator = function(socket, cid, callback) { user.isModerator(socket.uid, cid, callback); }; From 61efabf4ec1f0ba024e4c03dd751164c42400380 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Tue, 23 Aug 2016 09:03:30 -0400 Subject: [PATCH 091/386] Latest translations and fallbacks --- public/language/it/email.json | 4 +- public/language/it/error.json | 66 +++++++++++++-------------- public/language/it/global.json | 22 ++++----- public/language/it/groups.json | 2 +- public/language/it/login.json | 4 +- public/language/it/modules.json | 30 ++++++------ public/language/it/notifications.json | 22 ++++----- public/language/it/pages.json | 18 ++++---- public/language/it/recent.json | 2 +- public/language/it/register.json | 8 ++-- public/language/it/search.json | 4 +- public/language/it/topic.json | 32 ++++++------- public/language/it/unread.json | 6 +-- public/language/it/uploads.json | 8 ++-- public/language/it/user.json | 46 +++++++++---------- public/language/it/users.json | 2 +- 16 files changed, 138 insertions(+), 138 deletions(-) diff --git a/public/language/it/email.json b/public/language/it/email.json index ef28eac56e..8eddb0539d 100644 --- a/public/language/it/email.json +++ b/public/language/it/email.json @@ -17,7 +17,7 @@ "reset.notify.text1": "Ti informiamo che in data %1, la password è stata cambiata con successo.", "reset.notify.text2": "Se non hai autorizzato questo, per favore notifica immediatamente un amministratore.", "digest.notifications": "Hai una notifica non letta da %1:", - "digest.latest_topics": "Ultimi argomenti su %1", + "digest.latest_topics": "Ultime discussioni su %1", "digest.cta": "Clicca qui per visitare %1", "digest.unsub.info": "Questo sommario ti è stato inviato perché lo hai sottoscritto nelle tue impostazioni.", "digest.no_topics": "Non ci sono state discussioni attive nell'ultimo %1", @@ -28,7 +28,7 @@ "notif.chat.subject": "Nuovo messaggio in chat da %1", "notif.chat.cta": "Clicca qui per continuare la conversazione", "notif.chat.unsub.info": "Questa notifica di chat ti è stata inviata perché l'hai scelta nelle impostazioni.", - "notif.post.cta": "Clicca qui per leggere il topic completo.", + "notif.post.cta": "Clicca qui per leggere la discussione completa", "notif.post.unsub.info": "Questo post ti è stato notificato in base alle tue impostazioni di sottoscrizione.", "test.text1": "Questa è una email di test per verificare che il servizio di invio email è configurato correttamente sul tuo NodeBB.", "unsub.cta": "Clicca qui per modificare queste impostazioni", diff --git a/public/language/it/error.json b/public/language/it/error.json index 23b0710512..8fd8822deb 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -30,10 +30,10 @@ "password-too-long": "Password troppo lunga", "user-banned": "Utente bannato", "user-too-new": "Devi attendere %1 secondi prima di creare il tuo primo post", - "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", + "blacklisted-ip": "Purtroppo il tuo indirizzo IP è stato bannato da questa community. Se credi che ci sia stato un errore contatta un amministratore.", "ban-expiry-missing": "Please provide an end date for this ban", "no-category": "La Categoria non esiste", - "no-topic": "Il Topic non esiste", + "no-topic": "La discussione non esiste", "no-post": "Il Post non esiste", "no-group": "Il Gruppo non esiste", "no-user": "L'User non esiste", @@ -42,21 +42,21 @@ "category-disabled": "Categoria disabilitata", "topic-locked": "Discussione Bloccata", "post-edit-duration-expired": "Ti è consentito modificare un post per %1 secondi dopo averlo inviato", - "post-edit-duration-expired-minutes": "You are only allowed to edit posts for %1 minute(s) after posting", - "post-edit-duration-expired-minutes-seconds": "You are only allowed to edit posts for %1 minute(s) %2 second(s) after posting", - "post-edit-duration-expired-hours": "You are only allowed to edit posts for %1 hour(s) after posting", - "post-edit-duration-expired-hours-minutes": "You are only allowed to edit posts for %1 hour(s) %2 minute(s) after posting", - "post-edit-duration-expired-days": "You are only allowed to edit posts for %1 day(s) after posting", - "post-edit-duration-expired-days-hours": "You are only allowed to edit posts for %1 day(s) %2 hour(s) after posting", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", - "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", - "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", + "post-edit-duration-expired-minutes": "Ti è permesso modificare i post solo per %1 minuto(i) dopo averli inviati", + "post-edit-duration-expired-minutes-seconds": "Ti è permesso modificare i post solo per %1 minuto(i) %secondo(i) dopo averli inviati", + "post-edit-duration-expired-hours": "Ti è permesso modificare i post solo per %1 ora(e) dopo averli inviati", + "post-edit-duration-expired-hours-minutes": "Ti è permesso modificare i post solo per %1 ora(e) %2 minuto(i) dopo averli inviati", + "post-edit-duration-expired-days": "Ti è permesso modificare i post solo per %1 giorno(i) dopo averli inviati", + "post-edit-duration-expired-days-hours": "Ti è permesso modificare i post solo per %1 giorno(i) %2 ora(e) dopo averli inviati", + "post-delete-duration-expired": "Ti è permesso eliminare i post solo per %1 secondo(i) dopo averli inviati", + "post-delete-duration-expired-minutes": "Ti è permesso eliminare i post solo per %1 minuto(i) dopo averli inviati", + "post-delete-duration-expired-minutes-seconds": "Ti è permesso eliminare i post solo per %1 minuto(i) %secondo(i) dopo averli inviati", + "post-delete-duration-expired-hours": "Ti è permesso eliminare i post solo per %1 ora(e) dopo averli inviati", + "post-delete-duration-expired-hours-minutes": "Ti è permesso eliminare i post solo per %1 giorno(i) %2 ora(e) dopo averli inviati", + "post-delete-duration-expired-days": "Ti è permesso eliminare i post solo per %1 giorno(i) dopo averli inviati", + "post-delete-duration-expired-days-hours": "Ti è permesso eliminare i post solo per %1 giorno(i) %2 ora(e) dopo averli inviati", + "cant-delete-topic-has-reply": "Non puoi eliminare la tua discussione se ha una risposta", + "cant-delete-topic-has-replies": "Non puoi eliminare la tua discussione se ha %1 risposte", "content-too-short": "Inserisci un testo più lungo. Il messaggio deve contenere almeno %1 caratteri.", "content-too-long": "Inserisci un post più breve. I post non possono essere più lunghi di %1 caratteri.", "title-too-short": "Inserisci un titolo più lungo. I titoli devono contenere almeno %1 caratteri.", @@ -65,25 +65,25 @@ "too-many-posts-newbie": "Come nuovo utente puoi postare solamente una volta ogni %1 secondi finché non hai raggiunto un livello di reputazione %2 - per favore attendi prima di scrivere ancora", "tag-too-short": "Inserisci un tag più lungo. I tag devono contenere almeno %1 caratteri.", "tag-too-long": "Per favore inserisci un tag più corto. I tags non dovrebbero essere più lunghi di %1 caratteri", - "not-enough-tags": "Tag non sufficienti. Gli argomenti devono avere almeno %1 Tag", - "too-many-tags": "Troppi Tag. Gli argomenti non possono avere più di %1 Tag", + "not-enough-tags": "Tag non sufficienti. Le discussioni devono avere almeno %1 Tag", + "too-many-tags": "Troppi Tag. Le discussioni non possono avere più di %1 Tag", "still-uploading": "Per favore attendere il completamento degli uploads.", "file-too-big": "La dimensione massima consentita è di %1 kB - si prega di caricare un file più piccolo", - "guest-upload-disabled": "Guest uploading has been disabled", + "guest-upload-disabled": "Il caricamento da ospite è stato disattivato", "already-favourited": "You have already bookmarked this post", "already-unfavourited": "You have already unbookmarked this post", "cant-ban-other-admins": "Non puoi bannare altri amministratori!", "cant-remove-last-admin": "Sei l'unico Amministratore. Aggiungi un altro amministratore prima di rimuovere il tuo ruolo", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "Togli i privilegi amministrativi da questo account prima di provare ad eliminarlo.", "invalid-image-type": "Tipo dell'immagine non valido. I tipi permessi sono: %1", "invalid-image-extension": "Estensione immagine non valida", "invalid-file-type": "Tipo di file non valido. I formati consentiti sono: %1", "group-name-too-short": "Nome del Gruppo troppo corto", - "group-name-too-long": "Group name too long", + "group-name-too-long": "Il nome del gruppo è troppo lungo", "group-already-exists": "Il Gruppo esiste già", "group-name-change-not-allowed": "Il cambio di nome al Gruppo non è consentito", - "group-already-member": "Already part of this group", - "group-not-member": "Not a member of this group", + "group-already-member": "Fa già parte di questo gruppo", + "group-not-member": "Non è membro di questo gruppo", "group-needs-owner": "Questo gruppo richiede almeno un proprietario.", "group-already-invited": "Questo utente è già stato invitato", "group-already-requested": "La tua richiesta di partecipazione è già stata inviata", @@ -99,14 +99,14 @@ "about-me-too-long": "Spiacenti, il testo non può essere più lungo di %1 caratteri.", "cant-chat-with-yourself": "Non puoi chattare con te stesso!", "chat-restricted": "Questo utente ha ristretto i suoi messaggi in chat alle persone che segue. Per poter chattare con te ti deve prima seguire.", - "chat-disabled": "Chat system disabled", + "chat-disabled": "Il sistema di chat è stato disabilitato", "too-many-messages": "Hai inviato troppi messaggi, aspetta un attimo.", "invalid-chat-message": "Messaggio chat non valido", "chat-message-too-long": "Il messaggio chat è troppo lungo", - "cant-edit-chat-message": "You are not allowed to edit this message", - "cant-remove-last-user": "You can't remove the last user", - "cant-delete-chat-message": "You are not allowed to delete this message", - "already-voting-for-this-post": "You have already voted for this post.", + "cant-edit-chat-message": "Non ti è permesso di modificare questo messaggio", + "cant-remove-last-user": "Non puoi rimuovere l'ultimo utente", + "cant-delete-chat-message": "Non ti è permesso di eliminare questo messaggio", + "already-voting-for-this-post": "Hai già votato per questo post", "reputation-system-disabled": "Il sistema di reputazione è disabilitato.", "downvoting-disabled": "Il Downvoting è disabilitato", "not-enough-reputation-to-downvote": "Non hai i privilegi per votare negativamente questo post", @@ -119,9 +119,9 @@ "wrong-login-type-username": "Per favore usa il tuo nome utente per accedere", "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).", "no-session-found": "No login session found!", - "not-in-room": "User not in room", - "no-users-in-room": "No users in this room", - "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected", + "not-in-room": "L'utente non è in questa stanza", + "no-users-in-room": "Nessun utente in questa stanza", + "cant-kick-self": "Non puoi espellerti dal gruppo", + "no-users-selected": "Nessun utente selezionato", "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/it/global.json b/public/language/it/global.json index b20daeef18..ccfff6ad6e 100644 --- a/public/language/it/global.json +++ b/public/language/it/global.json @@ -49,7 +49,7 @@ "users": "Utenti", "topics": "Discussioni", "posts": "Post", - "best": "Best", + "best": "Migliore", "upvoters": "Upvoters", "upvoted": "Upvoted", "downvoters": "Downvoters", @@ -61,15 +61,15 @@ "posted_ago_by_guest": "scritto %1 di Ospite", "posted_ago_by": "scritto %1 di %2", "posted_ago": "postato %1", - "posted_in": "posted in %1", - "posted_in_by": "posted in %1 by %2", + "posted_in": "postato in %1", + "posted_in_by": "postato in %1 da %2", "posted_in_ago": "postato in %1 %2", "posted_in_ago_by": "postato in %1 %2 da %3", "user_posted_ago": "%1 ha postato %2", "guest_posted_ago": "Ospite ha scritto %1", - "last_edited_by": "last edited by %1", + "last_edited_by": "ultima modifica di %1", "norecentposts": "Nessun Post Recente", - "norecenttopics": "Nessun Argomento Recente", + "norecenttopics": "Nessuna Discussione Recente", "recentposts": "Post Recenti", "recentips": "Indirizzi IP Recentemente Loggati", "away": "Non disponibile", @@ -87,11 +87,11 @@ "unfollow": "Non seguire", "delete_all": "Elimina Tutto", "map": "Mappa", - "sessions": "Login Sessions", - "ip_address": "IP Address", - "enter_page_number": "Enter page number", - "upload_file": "Upload file", - "upload": "Upload", + "sessions": "Sessioni di Login", + "ip_address": "Indirizzo IP", + "enter_page_number": "Inserisci il numero della pagina", + "upload_file": "Carica file", + "upload": "Carica", "allowed-file-types": "Allowed file types are %1", - "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" + "unsaved-changes": "Hai delle modifiche non salvate. Sei sicuro che vuoi lasciare la pagina?" } \ No newline at end of file diff --git a/public/language/it/groups.json b/public/language/it/groups.json index ca2d7f898a..863b4be850 100644 --- a/public/language/it/groups.json +++ b/public/language/it/groups.json @@ -50,5 +50,5 @@ "membership.leave-group": "Lascia il Gruppo", "membership.reject": "Rifiuta", "new-group.group_name": "Nome Gruppo:", - "upload-group-cover": "Upload group cover" + "upload-group-cover": "Carica copertina del gruppo" } \ No newline at end of file diff --git a/public/language/it/login.json b/public/language/it/login.json index 3c875d4195..1561a8484b 100644 --- a/public/language/it/login.json +++ b/public/language/it/login.json @@ -5,8 +5,8 @@ "remember_me": "Ricordami?", "forgot_password": "Password dimenticata?", "alternative_logins": "Accessi Alternativi", - "failed_login_attempt": "Login Unsuccessful", + "failed_login_attempt": "Tentativo di accesso fallito", "login_successful": "Sei entrato con successo!", "dont_have_account": "Non hai un account?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Sei stato disconnesso dal Pannello di Controllo Amministratore per inattività" } \ No newline at end of file diff --git a/public/language/it/modules.json b/public/language/it/modules.json index d18485ef7d..2eaf84aba9 100644 --- a/public/language/it/modules.json +++ b/public/language/it/modules.json @@ -6,9 +6,9 @@ "chat.user_typing": "%1 sta scrivendo...", "chat.user_has_messaged_you": "%1 ti ha scritto.", "chat.see_all": "Vedi tutte le chat", - "chat.mark_all_read": "Mark all chats read", + "chat.mark_all_read": "Segna tutti i messaggi come già letti", "chat.no-messages": "Si prega di selezionare un destinatario per vedere la cronologia dei messaggi", - "chat.no-users-in-room": "No users in this room", + "chat.no-users-in-room": "Nessun utente in questa stanza", "chat.recent-chats": "Chat Recenti", "chat.contacts": "Contatti", "chat.message-history": "Cronologia Messaggi", @@ -17,9 +17,9 @@ "chat.seven_days": "7 Giorni", "chat.thirty_days": "30 Giorni", "chat.three_months": "3 Mesi", - "chat.delete_message_confirm": "Are you sure you wish to delete this message?", + "chat.delete_message_confirm": "Sei sicuro di voler eliminare questo messaggio?", "chat.roomname": "Chat Room %1", - "chat.add-users-to-room": "Add users to room", + "chat.add-users-to-room": "Aggiungi utenti alla stanza", "composer.compose": "Componi", "composer.show_preview": "Visualizza Anteprima", "composer.hide_preview": "Nascondi Anteprima", @@ -28,20 +28,20 @@ "composer.discard": "Sei sicuro di voler scartare questo post?", "composer.submit_and_lock": "Invia e Blocca", "composer.toggle_dropdown": "Mostra/Nascondi menu a discesa", - "composer.uploading": "Uploading %1", - "composer.formatting.bold": "Bold", - "composer.formatting.italic": "Italic", - "composer.formatting.list": "List", + "composer.uploading": "Caricamento %1", + "composer.formatting.bold": "Grassetto", + "composer.formatting.italic": "Corsivo", + "composer.formatting.list": "Lista", "composer.formatting.strikethrough": "Strikethrough", - "composer.formatting.link": "Link", - "composer.formatting.picture": "Picture", - "composer.upload-picture": "Upload Image", - "composer.upload-file": "Upload File", + "composer.formatting.link": "Collegamento", + "composer.formatting.picture": "Immagine", + "composer.upload-picture": "Carica immagine", + "composer.upload-file": "Carica file", "composer.zen_mode": "Zen Mode", "bootbox.ok": "OK", "bootbox.cancel": "Annulla", "bootbox.confirm": "Conferma", - "cover.dragging_title": "Cover Photo Positioning", - "cover.dragging_message": "Drag the cover photo to the desired position and click \"Save\"", - "cover.saved": "Cover photo image and position saved" + "cover.dragging_title": "Posizionando la foto copertina", + "cover.dragging_message": "Trascina l'immagine di copertina nella posizione desiderata e clicca su \"Salva\"", + "cover.saved": "Immagine di copertina e posizione salvati" } \ No newline at end of file diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json index bd224b2cea..c7725575b5 100644 --- a/public/language/it/notifications.json +++ b/public/language/it/notifications.json @@ -5,29 +5,29 @@ "mark_all_read": "Segna tutte le notifiche come già lette", "back_to_home": "Indietro a %1", "outgoing_link": "Link in uscita", - "outgoing_link_message": "You are now leaving %1", + "outgoing_link_message": "Stai lasciando %1", "continue_to": "Continua a %1", "return_to": "Ritorna a %1", "new_notification": "Nuova Notifica", "you_have_unread_notifications": "Hai notifiche non lette.", "new_message_from": "Nuovo messaggio da %1", "upvoted_your_post_in": "%1 ha votato positivamente il tuo post in %2.", - "upvoted_your_post_in_dual": "%1 and %2 have upvoted your post in %3.", - "upvoted_your_post_in_multiple": "%1 and %2 others have upvoted your post in %3.", - "moved_your_post": "%1 has moved your post to %2", - "moved_your_topic": "%1 has moved %2", + "upvoted_your_post_in_dual": "%1 e %2 hanno apprezzato il tuo post in %3.", + "upvoted_your_post_in_multiple": "%1 ed altri %2 hanno apprezzato il tuo post in %3.", + "moved_your_post": "%1 ha spostato il tuo post su %2", + "moved_your_topic": "%1 è stato spostato %2", "user_flagged_post_in": "%1 ha segnalato un post in %2", "user_flagged_post_in_dual": "%1 and %2 flagged a post in %3", "user_flagged_post_in_multiple": "%1 and %2 others flagged a post in %3", "user_posted_to": "%1 ha postato una risposta a: %2", - "user_posted_to_dual": "%1 and %2 have posted replies to: %3", - "user_posted_to_multiple": "%1 and %2 others have posted replies to: %3", - "user_posted_topic": "%1 ha postato un nuovo Topic: %2", + "user_posted_to_dual": "%1 e %2 hanno postato una risposta su: %3", + "user_posted_to_multiple": "%1 ed altri %2 hanno postato una risposta su: %3", + "user_posted_topic": "%1 ha postato una nuova discussione: %2", "user_started_following_you": "%1 ha iniziato a seguirti.", - "user_started_following_you_dual": "%1 and %2 started following you.", - "user_started_following_you_multiple": "%1 and %2 others started following you.", + "user_started_following_you_dual": "%1 e %2 hanno iniziato a seguirti.", + "user_started_following_you_multiple": "%1 ed altri %2 hanno iniziato a seguirti.", "new_register": "%1 ha inviato una richiesta di registrazione.", - "new_register_multiple": "There are %1 registration requests awaiting review.", + "new_register_multiple": "Ci sono %1 richieste di registrazione che attendono di essere esaminate.", "email-confirmed": "Email Confermata", "email-confirmed-message": "Grazie per aver validato la tua email. Il tuo account è ora completamente attivato.", "email-confirm-error-message": "C'è stato un problema nella validazione del tuo indirizzo email. Potrebbe essere il codice non valido o scaduto.", diff --git a/public/language/it/pages.json b/public/language/it/pages.json index da115161ee..e509b23ef8 100644 --- a/public/language/it/pages.json +++ b/public/language/it/pages.json @@ -11,7 +11,7 @@ "users/latest": "Ultimi Utenti", "users/sort-posts": "Utenti maggiori contributori", "users/sort-reputation": "Utenti con la reputazione più alta", - "users/banned": "Banned Users", + "users/banned": "Utenti Bannati", "users/most-flags": "Most flagged users", "users/search": "Ricerca Utenti", "notifications": "Notifiche", @@ -26,23 +26,23 @@ "chats": "Chat", "chat": "In chat con %1", "account/edit": "Modificando \"%1\"", - "account/edit/password": "Editing password of \"%1\"", - "account/edit/username": "Editing username of \"%1\"", - "account/edit/email": "Editing email of \"%1\"", + "account/edit/password": "Modificando la password di \"%1\"", + "account/edit/username": "Modificando il nome utente di \"%1\"", + "account/edit/email": "Modificando l'email di \"%1\"", "account/info": "Account Info", "account/following": "Persone seguite da %1", "account/followers": "Persone che seguono %1", "account/posts": "Post creati da %1", "account/topics": "Discussioni create da %1", "account/groups": "Gruppi di %1", - "account/favourites": "%1's Bookmarked Posts", + "account/favourites": "Post Preferiti da %1", "account/settings": "Impostazioni Utente", "account/watched": "Discussioni osservate da %1", - "account/upvoted": "Posts upvoted by %1", + "account/upvoted": "Post apprezzati da %1", "account/downvoted": "Posts downvoted by %1", - "account/best": "Best posts made by %1", - "confirm": "Email Confirmed", + "account/best": "I migliori post di %1", + "confirm": "Email Confermata", "maintenance.text": "%1 è attualmente in manutenzione. Per favore ritorna più tardi.", "maintenance.messageIntro": "Inoltre, l'amministratore ha lasciato questo messaggio:", - "throttled.text": "%1 is currently unavailable due to excessive load. Please come back another time." + "throttled.text": "%1 non è al momento disponibile a causa di un carico eccessivo. Per favore ritorna più tardi." } \ No newline at end of file diff --git a/public/language/it/recent.json b/public/language/it/recent.json index c61e51f150..a32a7b5d3c 100644 --- a/public/language/it/recent.json +++ b/public/language/it/recent.json @@ -6,7 +6,7 @@ "year": "Anno", "alltime": "Sempre", "no_recent_topics": "Non ci sono discussioni recenti.", - "no_popular_topics": "Non ci sono argomenti popolari.", + "no_popular_topics": "Non ci sono discussioni popolari.", "there-is-a-new-topic": "C'è un nuovo topic.", "there-is-a-new-topic-and-a-new-post": "C'è un nuovo topic e un nuovo post.", "there-is-a-new-topic-and-new-posts": "C'è una nuova discussione e %1 nuovi post.", diff --git a/public/language/it/register.json b/public/language/it/register.json index 58a04bf477..e3afd5aed2 100644 --- a/public/language/it/register.json +++ b/public/language/it/register.json @@ -1,6 +1,6 @@ { "register": "Registrazione", - "cancel_registration": "Cancel Registration", + "cancel_registration": "Cancella Registrazione", "help.email": "Come opzione predefinita, il tuo indirizzo email non verrà reso pubblico.", "help.username_restrictions": "Un nome utente unico, di almeno %1 caratteri e al massimo di %2. Gli altri utenti ti possono menzionare usando @username.", "help.minimum_password_length": "La lunghezza della password deve essere di almeno %1 caratteri.", @@ -16,8 +16,8 @@ "alternative_registration": "Altri metodi di registrazione", "terms_of_use": "Termini di Utilizzo", "agree_to_terms_of_use": "Accetto i Termini di Utilizzo", - "terms_of_use_error": "You must agree to the Terms of Use", + "terms_of_use_error": "Devi accettare i Termini d'Utilizzo", "registration-added-to-queue": "La tua registrazione è stata aggiunta alla coda di moderazione. Riceverai una mail quando verrà accettata da un amministratore.", - "interstitial.intro": "We require some additional information before we can create your account.", - "interstitial.errors-found": "We could not complete your registration:" + "interstitial.intro": "Abbiamo bisogno di qualche informazione in più prima di poter creare il tuo account.", + "interstitial.errors-found": "Non abbiamo potuto completare la tua registrazione:" } \ No newline at end of file diff --git a/public/language/it/search.json b/public/language/it/search.json index a3e73da887..9234c43389 100644 --- a/public/language/it/search.json +++ b/public/language/it/search.json @@ -24,10 +24,10 @@ "one-year": "Un anno", "sort-by": "Ordina per", "last-reply-time": "Ora dell'ultima risposta", - "topic-title": "Titolo argomento", + "topic-title": "Titolo discussione", "number-of-replies": "Numero di risposte", "number-of-views": "Numero di visite", - "topic-start-date": "Data inizio argomento", + "topic-start-date": "Discussione iniziata", "username": "Nome utente", "category": "Categoria", "descending": "In ordine decrescente", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index 1834ff19cd..5367678fa8 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -26,30 +26,30 @@ "tools": "Strumenti", "flag": "Segnala", "locked": "Bloccato", - "pinned": "Pinned", - "moved": "Moved", - "bookmark_instructions": "Click here to return to the last read post in this thread.", + "pinned": "Appeso", + "moved": "Spostato", + "bookmark_instructions": "Clicca qui per tornare all'ultimo post letto in questa discussione.", "flag_title": "Segnala questo post per la moderazione", "flag_success": "Questo post è stato contrassegnato per la moderazione.", "deleted_message": "Questa discussione è stata cancellata. Solo gli utenti con diritti di gestione possono vederla.", "following_topic.message": "Da ora riceverai notifiche quando qualcuno posterà in questa discussione.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Vedrai questa discussione nella lista delle discussioni non lette, ma non riceverai notifiche quando qualcuno risponde a questa discussione.", "ignoring_topic.message": "You will no longer see this topic in the unread topics list. You will be notified when you are mentioned or your post is up voted.", "login_to_subscribe": "Si prega di accedere o registrarsi per potersi iscrivere a questa discussione.", "markAsUnreadForAll.success": "Discussione segnata come non letta per tutti.", "mark_unread": "Segna come non letto", - "mark_unread.success": "Topic marked as unread.", + "mark_unread.success": "Discussione è stata marcata come non letta.", "watch": "Osserva", "unwatch": "Non osservare più", "watch.title": "Ricevi notifiche di nuove risposte in questa discussione", "unwatch.title": "Smetti di osservare questa discussione", "share_this_post": "Condividi questo Post", - "watching": "Watching", - "not-watching": "Not Watching", - "ignoring": "Ignoring", + "watching": "Seguito", + "not-watching": "Non Seguito", + "ignoring": "Ignorato", "watching.description": "Notify me of new replies.
    Show topic in unread.", - "not-watching.description": "Do not notify me of new replies.
    Show topic in unread if category is not ignored.", - "ignoring.description": "Do not notify me of new replies.
    Do not show topic in unread.", + "not-watching.description": "Non notificarmi sulle nuove risposte.
    Mostra la discussione fra le non lette se la categoria non è ignorata.", + "ignoring.description": "Non notificarmi sulle nuove risposte.
    Non mostrare la discussione fra le non lette.", "thread_tools.title": "Strumenti per la Discussione", "thread_tools.markAsUnreadForAll": "Segna come non letto", "thread_tools.pin": "Fissa Discussione", @@ -61,7 +61,7 @@ "thread_tools.fork": "Dividi Discussione", "thread_tools.delete": "Elimina Discussione", "thread_tools.delete-posts": "Cancella post", - "thread_tools.delete_confirm": "Sei sicuro di voler cancellare questa discussione?", + "thread_tools.delete_confirm": "Sei sicuro di voler eliminare questa discussione?", "thread_tools.restore": "Ripristina Discussione", "thread_tools.restore_confirm": "Sei sicuro di voler ripristinare questa discussione?", "thread_tools.purge": "Svuota Discussione", @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Questa discussione verrà spostata nella categoria", "fork_topic_instruction": "Clicca sui post che vuoi dividere", "fork_no_pids": "Nessun post selezionato!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 post selezionati", "fork_success": "Topic Diviso con successo ! Clicca qui per andare al Topic Diviso.", "delete_posts_instruction": "Clicca sui post che vuoi cancellare/eliminare", "composer.title_placeholder": "Inserisci qui il titolo della discussione...", @@ -94,7 +94,7 @@ "composer.discard": "Annulla", "composer.submit": "Invia", "composer.replying_to": "Rispondendo a %1", - "composer.new_topic": "Nuovo Argomento", + "composer.new_topic": "Nuova Discussione", "composer.uploading": "caricamento...", "composer.thumb_url_label": "Incolla l'URL di una immagine per la discussione", "composer.thumb_title": "Aggiungi un'immagine a questa discussione", @@ -111,10 +111,10 @@ "newest_to_oldest": "Da Nuovi a Vecchi", "most_votes": "Più votati", "most_posts": "Ulteriori post", - "stale.title": "Preferisci creare un nuovo topic?", + "stale.title": "Preferisci creare una nuova discussione?", "stale.warning": "Il topic al quale stai rispondendo è abbastanza vecchio. Vorresti piuttosto creare un nuovo topic in riferimento a questo nella tua risposta?", - "stale.create": "Crea un topic nuovo", - "stale.reply_anyway": "Rispondi a questo topic comunque", + "stale.create": "Crea una nuova discussione", + "stale.reply_anyway": "Rispondi comunque a questa discussione", "link_back": "Re: [%1](%2)", "spam": "Spam", "offensive": "Offensivo", diff --git a/public/language/it/unread.json b/public/language/it/unread.json index 94c4a315a3..f03f876183 100644 --- a/public/language/it/unread.json +++ b/public/language/it/unread.json @@ -7,7 +7,7 @@ "all": "Tutti", "all_categories": "Tutte le categorie", "topics_marked_as_read.success": "Discussione marcata come letta!", - "all-topics": "All Topics", - "new-topics": "New Topics", - "watched-topics": "Watched Topics" + "all-topics": "Tutte le Discussioni", + "new-topics": "Nuova Discussione", + "watched-topics": "Discussioni seguite" } \ No newline at end of file diff --git a/public/language/it/uploads.json b/public/language/it/uploads.json index 1622cb5693..fcc163f5c7 100644 --- a/public/language/it/uploads.json +++ b/public/language/it/uploads.json @@ -1,6 +1,6 @@ { - "uploading-file": "Uploading the file...", - "select-file-to-upload": "Select a file to upload!", - "upload-success": "File uploaded successfully!", - "maximum-file-size": "Maximum %1 kb" + "uploading-file": "Sto caricando il file...", + "select-file-to-upload": "Seleziona un file da caricare!", + "upload-success": "File caricato con successo!", + "maximum-file-size": "Massimo %1 kb" } \ No newline at end of file diff --git a/public/language/it/user.json b/public/language/it/user.json index 1c864d034b..939f0e0923 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -6,7 +6,7 @@ "postcount": "Numero post", "email": "Email", "confirm_email": "Conferma Email", - "account_info": "Account Info", + "account_info": "Informazioni dell'account", "ban_account": "BAN dell'account", "ban_account_confirm": "Sei sicuro di voler bannare questo utente?", "unban_account": "Togli il BAN", @@ -23,7 +23,7 @@ "profile": "Profilo", "profile_views": "Visite al profilo", "reputation": "Reputazione", - "favourites": "Bookmarks", + "favourites": "Preferiti", "watched": "Osservati", "followers": "Da chi è seguito", "following": "Chi segue", @@ -31,17 +31,17 @@ "signature": "Firma", "birthday": "Data di nascita", "chat": "Chat", - "chat_with": "Chat with %1", + "chat_with": "Chatta con %1", "follow": "Segui", "unfollow": "Smetti di seguire", "more": "Altro", "profile_update_success": "Profilo aggiornato correttamente!", "change_picture": "Cambia Foto", - "change_username": "Change Username", - "change_email": "Change Email", + "change_username": "Modifica il nome utente", + "change_email": "Modifica Email", "edit": "Modifica", - "edit-profile": "Edit Profile", - "default_picture": "Default Icon", + "edit-profile": "Modifica Profilo", + "default_picture": "Icona di default", "uploaded_picture": "Foto caricata", "upload_new_picture": "Carica una nuova foto", "upload_new_picture_from_url": "Carica nuova immagine da URL", @@ -57,17 +57,17 @@ "password": "Password", "username_taken_workaround": "Il nome utente che hai richiesto era già stato utilizzato, quindi lo abbiamo modificato leggermente. Ora il tuo è %1", "password_same_as_username": "La tua password è uguale al tuo username, per piacere scegli un'altra password", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "password_same_as_email": "La tua password sembra coincidere con la tua email, per favore fornisci un'altra password.", "upload_picture": "Carica foto", "upload_a_picture": "Carica una foto", "remove_uploaded_picture": "Elimina foto caricata", - "upload_cover_picture": "Upload cover picture", + "upload_cover_picture": "Carica immagine di copertina", "settings": "Impostazioni", "show_email": "Mostra la mia Email", "show_fullname": "Vedi il Mio Nome Completo", "restrict_chats": "Abilita messaggi in chat soltanto dagli utenti che seguo", "digest_label": "Iscriviti al Sommario", - "digest_description": "Abbonati agli aggiornamenti via email di questo forum (nuove notifiche e argomenti) secondo una pianificazione impostata", + "digest_description": "Abbonati agli aggiornamenti via email di questo forum (nuove notifiche e discussioni) secondo una pianificazione impostata", "digest_off": "Spento", "digest_daily": "Quotidiano", "digest_weekly": "Settimanale", @@ -82,7 +82,7 @@ "has_no_watched_topics": "Questo utente non sta osservando discussioni.", "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.", "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.", - "has_no_voted_posts": "This user has no voted posts", + "has_no_voted_posts": "Questo utente non ha post votati", "email_hidden": "Email Nascosta", "hidden": "nascosta", "paginate_description": "Non utilizzare lo scroll infinito per discussioni e messaggi", @@ -94,16 +94,16 @@ "enable_topic_searching": "Abilita la ricerca negli argomenti", "topic_search_help": "Se abilitata, la ricerca negli argomenti ignorerà il comportamento predefinito del browser per consentirti di cercare all'interno delle discussioni, anziché soltanto nel contenuto visibile a schermo", "delay_image_loading": "Delay Image Loading", - "image_load_delay_help": "If enabled, images in topics will not load until they are scrolled into view", - "scroll_to_my_post": "After posting a reply, show the new post", - "follow_topics_you_reply_to": "Watch topics that you reply to", - "follow_topics_you_create": "Watch topics you create", - "grouptitle": "Group Title", + "image_load_delay_help": "Se selezionato, le immagini nelle discussioni non saranno caricate finché non sono visibili nello schermo", + "scroll_to_my_post": "Dopo aver postato una risposta, mostra il nuovo post", + "follow_topics_you_reply_to": "Segui le discussioni a cui rispondi", + "follow_topics_you_create": "Segui le discussioni che crei", + "grouptitle": "Titolo del Gruppo", "no-group-title": "Nessun titolo al gruppo", "select-skin": "Seleziona uno Skin", - "select-homepage": "Select a Homepage", - "homepage": "Homepage", - "homepage_description": "Select a page to use as the forum homepage or 'None' to use the default homepage.", + "select-homepage": "Seleziona una Pagina Iniziale", + "homepage": "Pagina iniziale", + "homepage_description": "Seleziona una pagina da usare come pagina iniziale o \"Nessuna\" per usare quella di default.", "custom_route": "Custom Homepage Route", "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")", "sso.title": "Servizi Single-Sign-On", @@ -111,8 +111,8 @@ "sso.not-associated": "Clicca qui per associare con", "info.latest-flags": "Latest Flags", "info.no-flags": "No Flagged Posts Found", - "info.ban-history": "Recent Ban History", - "info.no-ban-history": "This user has never been banned", - "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.ban-history": "Storico dei Ban recenti", + "info.no-ban-history": "Questo utente non è mai stato bannato", + "info.banned-until": "Bannato fino %1", + "info.banned-permanently": "Bannato permanentemente" } \ No newline at end of file diff --git a/public/language/it/users.json b/public/language/it/users.json index 3299cd0459..c88f786172 100644 --- a/public/language/it/users.json +++ b/public/language/it/users.json @@ -17,5 +17,5 @@ "unread_topics": "Discussioni non lette", "categories": "Categorie", "tags": "Tag", - "no-users-found": "No users found!" + "no-users-found": "Nessun utente trovato!" } \ No newline at end of file From 31751c9d8a877c447b8a0669ea288c511e1acec6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Aug 2016 18:47:07 +0300 Subject: [PATCH 092/386] closes #4960 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4102742aab..b37e83e63d 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.23", - "nodebb-theme-vanilla": "5.1.11", + "nodebb-theme-persona": "4.1.24", + "nodebb-theme-vanilla": "5.1.12", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From 5d5b74f3edcb6afd0b7430a9836d8c8dd0b7eae8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Aug 2016 19:59:25 +0300 Subject: [PATCH 093/386] if no sorting specificed let search plugins handle --- src/search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.js b/src/search.js index 9b7306c7fa..b34704bf47 100644 --- a/src/search.js +++ b/src/search.js @@ -298,10 +298,10 @@ function filterByTimerange(posts, timeRange, timeFilter) { } function sortPosts(posts, data) { - if (!posts.length) { + if (!posts.length || !data.sortBy) { return; } - data.sortBy = data.sortBy || 'timestamp'; + data.sortDirection = data.sortDirection || 'desc'; var direction = data.sortDirection === 'desc' ? 1 : -1; From 34276dd638d5b883b416040f3ddced5dcf4067d9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 23 Aug 2016 20:04:25 +0300 Subject: [PATCH 094/386] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b37e83e63d..1f452613a3 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.24", - "nodebb-theme-vanilla": "5.1.12", + "nodebb-theme-persona": "4.1.25", + "nodebb-theme-vanilla": "5.1.13", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From dbdbfc6d7537a2a1b0314c121b0e627e02565b2c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Aug 2016 15:10:46 -0400 Subject: [PATCH 095/386] allowing the port to be defined by a custom port environment variable, for certain hosting environments --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 7fdee397f9..c633af9932 100644 --- a/app.js +++ b/app.js @@ -124,7 +124,7 @@ function start() { nconf.set('secure', urlObject.protocol === 'https:'); nconf.set('use_port', !!urlObject.port); nconf.set('relative_path', relativePath); - nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || 4567); + nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); nconf.set('upload_url', nconf.get('upload_path').replace(/^\/public/, '')); if (nconf.get('isPrimary') === 'true') { From 598935b3bfbca89fe176c7f2050325e6de6811d9 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 23 Aug 2016 19:35:50 -0500 Subject: [PATCH 096/386] Fix null object in search for users with no preferences set. localStorage.getItem returned null, and JSON.parse(null) is null. --- public/src/modules/search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/src/modules/search.js b/public/src/modules/search.js index be6ac6a711..147683c550 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -75,7 +75,7 @@ define('search', ['navigator', 'translator'], function(nav, translator) { Search.getSearchPreferences = function() { try { - return JSON.parse(localStorage.getItem('search-preferences')); + return JSON.parse(localStorage.getItem('search-preferences') || '{}'); } catch(e) { return {}; } @@ -179,4 +179,4 @@ define('search', ['navigator', 'translator'], function(nav, translator) { }; return Search; -}); \ No newline at end of file +}); From 0fb74a2636823f9b2add0960bc4d757f5fdbe37c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 24 Aug 2016 12:14:28 +0300 Subject: [PATCH 097/386] pass err object along so errors dont get treated as 404 @julianlam --- src/controllers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index 47047924a4..975dca54ae 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -410,7 +410,7 @@ Controllers.handleURIErrors = function(err, req, res, next) { return; } else { - next(); + next(err); } }; From 84f88a6f15126250bdfcf666c20be28de4a0d45a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 24 Aug 2016 14:47:01 +0300 Subject: [PATCH 098/386] refactor user search use pagination on results removed infinite scroll changed the term and section to use the query param as well pagination urls respect search --- public/src/client/account/followers.js | 34 +----- public/src/client/account/following.js | 6 +- public/src/client/search.js | 17 ++- public/src/client/users.js | 145 +++++++++---------------- public/src/modules/search.js | 15 +-- src/controllers/accounts/follow.js | 20 +++- src/controllers/accounts/helpers.js | 14 ++- src/controllers/search.js | 4 +- src/controllers/users.js | 79 +++++++++++--- src/routes/index.js | 27 ++--- src/socket.io/user.js | 45 -------- src/user/search.js | 11 +- 12 files changed, 181 insertions(+), 236 deletions(-) diff --git a/public/src/client/account/followers.js b/public/src/client/account/followers.js index 91d3f4daaa..8a2d15f0b0 100644 --- a/public/src/client/account/followers.js +++ b/public/src/client/account/followers.js @@ -1,43 +1,13 @@ 'use strict'; -/* globals define, socket, utils */ +/* globals define */ -define('forum/account/followers', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) { +define('forum/account/followers', ['forum/account/header'], function(header) { var Followers = {}; Followers.init = function() { header.init(); - - infinitescroll.init(function(direction) { - Followers.loadMore(direction, 'account/followers', 'followers:' + ajaxify.data.uid); - }); }; - Followers.loadMore = function(direction, tpl, set) { - if (direction < 0) { - return; - } - - infinitescroll.loadMore('user.loadMore', { - set: set, - after: $('#users-container').attr('data-nextstart') - }, function(data, done) { - if (data.users && data.users.length) { - onUsersLoaded(tpl, data.users, done); - $('#users-container').attr('data-nextstart', data.nextStart); - } else { - done(); - } - }); - }; - - function onUsersLoaded(tpl, users, callback) { - app.parseAndTranslate(tpl, 'users', {users: users}, function(html) { - $('#users-container').append(html); - utils.addCommasToNumbers(html.find('.formatted-number')); - callback(); - }); - } - return Followers; }); diff --git a/public/src/client/account/following.js b/public/src/client/account/following.js index 8e421b1892..04230fb979 100644 --- a/public/src/client/account/following.js +++ b/public/src/client/account/following.js @@ -2,15 +2,11 @@ /* globals define */ -define('forum/account/following', ['forum/account/header', 'forum/infinitescroll', 'forum/account/followers'], function(header, infinitescroll, followers) { +define('forum/account/following', ['forum/account/header'], function(header) { var Following = {}; Following.init = function() { header.init(); - - infinitescroll.init(function(direction) { - followers.loadMore(direction, 'account/following', 'following:' + ajaxify.data.uid); - }); }; return Following; diff --git a/public/src/client/search.js b/public/src/client/search.js index bf07ad70b6..b9632513bc 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -22,15 +22,10 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco $('#advanced-search').off('submit').on('submit', function(e) { e.preventDefault(); - - var input = $('#search-input'); - - var searchData = getSearchData(); - searchData.term = input.val(); - - searchModule.query(searchData, function() { - input.val(''); + searchModule.query(getSearchData(), function() { + $('#search-input').val(''); }); + return false; }); handleSavePreferences(); @@ -43,7 +38,7 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco var searchData = { in: $('#search-in').val() }; - + searchData.term = $('#search-input').val(); if (searchData.in === 'posts' || searchData.in === 'titlesposts' || searchData.in === 'titles') { searchData.by = form.find('#posted-by-user').val(); searchData.categories = form.find('#posted-in-categories').val(); @@ -71,6 +66,10 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco params = utils.merge(searchData, params); if (params) { + if (params.term) { + $('#search-input').val(params.term); + } + if (params.in) { $('#search-in').val(params.in); updateFormItemVisiblity(params.in); diff --git a/public/src/client/users.js b/public/src/client/users.js index 40f221e33f..17016fc7b0 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -1,16 +1,24 @@ 'use strict'; -/* globals define, socket, app, templates, bootbox, ajaxify */ +/* globals define, socket, app, templates, bootbox, utils */ define('forum/users', ['translator'], function(translator) { var Users = {}; - var loadingMoreUsers = false; + var searchTimeoutID = 0; + + $(window).on('action:ajaxify.start', function() { + if (searchTimeoutID) { + clearTimeout(searchTimeoutID); + searchTimeoutID = 0; + } + }); Users.init = function() { app.enterRoom('user_list'); - $('.nav-pills li').removeClass('active').find('a[href="' + window.location.pathname + '"]').parent().addClass('active'); + var section = utils.params().section ? ('?section=' + utils.params().section) : ''; + $('.nav-pills li').removeClass('active').find('a[href="' + window.location.pathname + section + '"]').parent().addClass('active'); handleSearch(); @@ -18,110 +26,55 @@ define('forum/users', ['translator'], function(translator) { socket.removeListener('event:user_status_change', onUserStatusChange); socket.on('event:user_status_change', onUserStatusChange); - - $('#load-more-users-btn').on('click', loadMoreUsers); - - $(window).off('scroll').on('scroll', function() { - var bottom = ($(document).height() - $(window).height()) * 0.9; - - if ($(window).scrollTop() > bottom && !loadingMoreUsers) { - loadMoreUsers(); - } - }); }; - function loadMoreUsers() { - if ($('#search-user').val()) { - return; - } - - if (ajaxify.data.setName) { - startLoading(ajaxify.data.setName, $('#users-container').children('.registered-user').length); - } - } - - function startLoading(set, after) { - loadingMoreUsers = true; - - socket.emit('user.loadMore', { - set: set, - after: after - }, function(err, data) { - if (err) { - return app.alertError(err.message); - } - - if (data && data.users.length) { - onUsersLoaded(data); - $('#load-more-users-btn').removeClass('disabled'); - } else { - $('#load-more-users-btn').addClass('disabled'); - } - loadingMoreUsers = false; - }); - } - - function onUsersLoaded(data) { - data.users = data.users.filter(function(user) { - return !$('.users-box[data-uid="' + user.uid + '"]').length; - }); - - templates.parse('users', 'users', data, function(html) { - translator.translate(html, function(translated) { - translated = $(translated); - $('#users-container').append(translated); - translated.find('span.timeago').timeago(); - utils.addCommasToNumbers(translated.find('.formatted-number')); - $('#users-container .anon-user').appendTo($('#users-container')); - }); - }); - } - function handleSearch() { - var timeoutId = 0; + searchTimeoutID = 0; $('#search-user').on('keyup', function() { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = 0; + if (searchTimeoutID) { + clearTimeout(searchTimeoutID); + searchTimeoutID = 0; } - timeoutId = setTimeout(doSearch, 250); + searchTimeoutID = setTimeout(doSearch, 150); }); $('.search select, .search input[type="checkbox"]').on('change', function() { doSearch(); }); - - $('.users').on('click', '.pagination a', function() { - doSearch($(this).attr('data-page')); - return false; - }); } function doSearch(page) { + $('[component="user/search/icon"]').removeClass('fa-search').addClass('fa-spinner fa-spin'); var username = $('#search-user').val(); page = page || 1; - if (!username) { - return loadPage(page); - } var activeSection = getActiveSection(); - socket.emit('user.search', { - query: username, - page: page, - searchBy: 'username', - sortBy: $('.search select').val() || getSortBy(), - onlineOnly: $('.search .online-only').is(':checked') || (activeSection === 'online'), - bannedOnly: activeSection === 'banned', - flaggedOnly: activeSection === 'flagged' - }, function(err, data) { - if (err) { - return app.alertError(err.message); - } - renderSearchResults(data); - }); + var query = { + section: activeSection, + page: page + }; + + if (!username) { + return loadPage(query); + } + + query.term = username; + query.sortBy = getSortBy(); + + if ($('.search .online-only').is(':checked') || (activeSection === 'online')) { + query.onlineOnly = true; + } + if (activeSection === 'banned') { + query.bannedOnly = true; + } + if (activeSection === 'flagged') { + query.flaggedOnly = true; + } + + loadPage(query); } function getSortBy() { @@ -137,16 +90,17 @@ define('forum/users', ['translator'], function(translator) { return sortBy; } - function loadPage(page) { - var section = getActiveSection(); - section = section !== 'users' ? section : ''; - $.get('/api/users/' + section + '?page=' + page, function(data) { - renderSearchResults(data); + + function loadPage(query) { + var qs = decodeURIComponent($.param(query)); + $.get('/api/users?' + qs, renderSearchResults).fail(function(xhrErr) { + if (xhrErr && xhrErr.responseJSON && xhrErr.responseJSON.error) { + app.alertError(xhrErr.responseJSON.error); + } }); } function renderSearchResults(data) { - $('#load-more-users-btn').addClass('hide'); templates.parse('partials/paginator', {pagination: data.pagination}, function(html) { $('.pagination-container').replaceWith(html); }); @@ -156,6 +110,7 @@ define('forum/users', ['translator'], function(translator) { translated = $(translated); $('#users-container').html(translated); translated.find('span.timeago').timeago(); + $('[component="user/search/icon"]').addClass('fa-search').removeClass('fa-spinner fa-spin'); }); }); } @@ -173,9 +128,7 @@ define('forum/users', ['translator'], function(translator) { } function getActiveSection() { - var url = window.location.href; - var parts = url.split('/'); - return parts[parts.length - 1]; + return utils.params().section || ''; } function handleInvite() { diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 147683c550..304235c7fa 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -4,8 +4,8 @@ define('search', ['navigator', 'translator'], function(nav, translator) { var Search = { - current: {} - }; + current: {} + }; Search.query = function(data, callback) { var term = data.term; @@ -22,11 +22,11 @@ define('search', ['navigator', 'translator'], function(nav, translator) { return app.alertError('[[error:invalid-search-term]]'); } - ajaxify.go('search/' + term + '?' + createQueryString(data)); + ajaxify.go('search?' + createQueryString(data)); callback(); } else { - var cleanedTerm = term.replace(topicSearch[0], ''), - tid = topicSearch[1]; + var cleanedTerm = term.replace(topicSearch[0], ''); + var tid = topicSearch[1]; if (cleanedTerm.length > 0) { Search.queryTopic(tid, cleanedTerm, callback); @@ -38,8 +38,9 @@ define('search', ['navigator', 'translator'], function(nav, translator) { var searchIn = data['in'] || 'titlesposts'; var postedBy = data.by || ''; var query = { - 'in': searchIn - }; + term: data.term, + 'in': searchIn + }; if (postedBy && (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts')) { query.by = postedBy; diff --git a/src/controllers/accounts/follow.js b/src/controllers/accounts/follow.js index f9dc72c6f3..5897347a12 100644 --- a/src/controllers/accounts/follow.js +++ b/src/controllers/accounts/follow.js @@ -1,10 +1,11 @@ 'use strict'; -var async = require('async'), +var async = require('async'); - user = require('../../user'), - helpers = require('../helpers'), - accountHelpers = require('./helpers'); +var user = require('../../user'); +var helpers = require('../helpers'); +var accountHelpers = require('./helpers'); +var pagination = require('../../pagination'); var followController = {}; @@ -19,6 +20,11 @@ followController.getFollowers = function(req, res, next) { function getFollow(tpl, name, req, res, callback) { var userData; + var page = parseInt(req.query.page, 10) || 1; + var resultsPerPage = 50; + var start = Math.max(0, page - 1) * resultsPerPage; + var stop = start + resultsPerPage - 1; + async.waterfall([ function(next) { accountHelpers.getBaseUser(req.params.userslug, req.uid, next); @@ -29,7 +35,7 @@ function getFollow(tpl, name, req, res, callback) { return callback(); } var method = name === 'following' ? 'getFollowing' : 'getFollowers'; - user[method](userData.uid, 0, 49, next); + user[method](userData.uid, start, stop, next); } ], function(err, users) { if (err) { @@ -37,8 +43,10 @@ function getFollow(tpl, name, req, res, callback) { } userData.users = users; - userData.nextStart = 50; userData.title = '[[pages:' + tpl + ', ' + userData.username + ']]'; + var count = name === 'following' ? userData.followingCount : userData.followerCount; + var pageCount = Math.ceil(count / resultsPerPage); + userData.pagination = pagination.create(page, pageCount); userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:' + name + ']]'}]); res.render(tpl, userData); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 12a891a673..2b1cac23c3 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -133,7 +133,19 @@ helpers.getBaseUser = function(userslug, callerUID, callback) { async.parallel({ user: function(next) { - user.getUserFields(uid, ['uid', 'username', 'userslug', 'picture', 'cover:url', 'cover:position', 'status', 'lastonline', 'groupTitle'], next); + user.getUserFields(uid, [ + 'uid', + 'username', + 'userslug', + 'picture', + 'cover:url', + 'cover:position', + 'status', + 'lastonline', + 'groupTitle', + 'followingCount', + 'followerCount' + ], next); }, isAdmin: function(next) { user.isAdministrator(callerUID, next); diff --git a/src/controllers/search.js b/src/controllers/search.js index 2a7f6b145c..2466f17670 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -28,7 +28,7 @@ searchController.search = function(req, res, next) { } var data = { - query: req.params.term, + query: req.query.term, searchIn: req.query.in || 'posts', postedBy: req.query.by, categories: req.query.categories, @@ -59,7 +59,7 @@ searchController.search = function(req, res, next) { searchData.showAsTopics = req.query.showAs === 'topics'; searchData.title = '[[global:header.search]]'; searchData.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:search]]'}]); - searchData.expandSearch = !req.params.term; + searchData.expandSearch = !req.query.term; res.render('search', searchData); }); diff --git a/src/controllers/users.js b/src/controllers/users.js index 20ea62ca40..4f503bb6a3 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -11,10 +11,61 @@ var helpers = require('./helpers'); var usersController = {}; + +usersController.index = function(req, res, next) { + var section = req.query.section || 'joindate'; + var sectionToController = { + joindate: usersController.getUsersSortedByJoinDate, + online: usersController.getOnlineUsers, + 'sort-posts': usersController.getUsersSortedByPosts, + 'sort-reputation': usersController.getUsersSortedByReputation, + banned: usersController.getBannedUsers, + flagged: usersController.getFlaggedUsers + }; + + if (req.query.term) { + usersController.search(req, res, next); + } else if (sectionToController[section]) { + sectionToController[section](req, res, next); + } else { + usersController.getUsersSortedByJoinDate(req, res, next); + } +}; + +usersController.search = function(req, res, next) { + async.parallel({ + search: function(next) { + user.search({ + query: req.query.term, + searchBy: req.query.searchBy || 'username', + page: req.query.page || 1, + sortBy: req.query.sortBy, + onlineOnly: req.query.onlineOnly === 'true', + bannedOnly: req.query.bannedOnly === 'true', + flaggedOnly: req.query.flaggedOnly === 'true' + }, next); + }, + isAdminOrGlobalMod: function(next) { + user.isAdminOrGlobalMod(req.uid, next); + } + }, function(err, results) { + if (err) { + return next(err); + } + + var section = req.query.section || 'joindate'; + + results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod; + results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query); + results.search['section_' + section] = true; + render(req, res, results.search, next); + }); +}; + usersController.getOnlineUsers = function(req, res, next) { async.parallel({ users: function(next) { - usersController.getUsers('users:online', req.uid, req.query.page, next); + usersController.getUsers('users:online', req.uid, req.query, next); }, guests: function(next) { require('../socket.io/admin/rooms').getTotalGuestCount(next); @@ -56,7 +107,7 @@ usersController.getUsersSortedByJoinDate = function(req, res, next) { }; usersController.getBannedUsers = function(req, res, next) { - usersController.getUsers('users:banned', req.uid, req.query.page, function(err, userData) { + usersController.getUsers('users:banned', req.uid, req.query, function(err, userData) { if (err) { return next(err); } @@ -70,7 +121,7 @@ usersController.getBannedUsers = function(req, res, next) { }; usersController.getFlaggedUsers = function(req, res, next) { - usersController.getUsers('users:flags', req.uid, req.query.page, function(err, userData) { + usersController.getUsers('users:flags', req.uid, req.query, function(err, userData) { if (err) { return next(err); } @@ -84,15 +135,16 @@ usersController.getFlaggedUsers = function(req, res, next) { }; usersController.renderUsersPage = function(set, req, res, next) { - usersController.getUsers(set, req.uid, req.query.page, function(err, userData) { + usersController.getUsers(set, req.uid, req.query, function(err, userData) { if (err) { return next(err); } + render(req, res, userData, next); }); }; -usersController.getUsers = function(set, uid, page, callback) { +usersController.getUsers = function(set, uid, query, callback) { var setToData = { 'users:postcount': {title: '[[pages:users/sort-posts]]', crumb: '[[users:top_posters]]'}, 'users:reputation': {title: '[[pages:users/sort-reputation]]', crumb: '[[users:most_reputation]]'}, @@ -112,17 +164,14 @@ usersController.getUsers = function(set, uid, page, callback) { breadcrumbs.unshift({text: '[[global:users]]', url: '/users'}); } - page = parseInt(page, 10) || 1; + var page = parseInt(query.page, 10) || 1; var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 50; var start = Math.max(0, page - 1) * resultsPerPage; var stop = start + resultsPerPage - 1; async.parallel({ - isAdministrator: function(next) { - user.isAdministrator(uid, next); - }, - isGlobalMod: function(next) { - user.isGlobalModerator(uid, next); + isAdminOrGlobalMod: function(next) { + user.isAdminOrGlobalMod(uid, next); }, usersData: function(next) { usersController.getUsersAndCount(set, uid, start, stop, next); @@ -134,16 +183,14 @@ usersController.getUsers = function(set, uid, page, callback) { var pageCount = Math.ceil(results.usersData.count / resultsPerPage); var userData = { - loadmore_display: results.usersData.count > (stop - start + 1) ? 'block' : 'hide', users: results.usersData.users, - pagination: pagination.create(page, pageCount), + pagination: pagination.create(page, pageCount, query), userCount: results.usersData.count, title: setToData[set].title || '[[pages:users/latest]]', breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs), - setName: set, - isAdminOrGlobalMod: results.isAdministrator || results.isGlobalMod + isAdminOrGlobalMod: results.isAdminOrGlobalMod }; - userData['route_' + set] = true; + userData['section_' + (query.section || 'joindate')] = true; callback(null, userData); }); }; diff --git a/src/routes/index.js b/src/routes/index.js index 006e5b8c18..00e0ea0e77 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -33,7 +33,7 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/compose', middleware, [], controllers.compose); setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail); setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing); - setupPageRoute(app, '/search/:term?', middleware, [], controllers.search.search); + setupPageRoute(app, '/search', middleware, [], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); @@ -73,12 +73,7 @@ function categoryRoutes(app, middleware, controllers) { function userRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings]; - setupPageRoute(app, '/users', middleware, middlewares, controllers.users.getUsersSortedByJoinDate); - setupPageRoute(app, '/users/online', middleware, middlewares, controllers.users.getOnlineUsers); - setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts); - setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation); - setupPageRoute(app, '/users/banned', middleware, middlewares, controllers.users.getBannedUsers); - setupPageRoute(app, '/users/flagged', middleware, middlewares, controllers.users.getFlaggedUsers); + setupPageRoute(app, '/users', middleware, middlewares, controllers.users.index); } function groupRoutes(app, middleware, controllers) { @@ -91,15 +86,15 @@ function groupRoutes(app, middleware, controllers) { module.exports = function(app, middleware, hotswapIds) { var routers = [ - express.Router(), // plugin router - express.Router(), // main app router - express.Router() // auth router - ], - router = routers[1], - pluginRouter = routers[0], - authRouter = routers[2], - relativePath = nconf.get('relative_path'), - ensureLoggedIn = require('connect-ensure-login'); + express.Router(), // plugin router + express.Router(), // main app router + express.Router() // auth router + ]; + var router = routers[1]; + var pluginRouter = routers[0]; + var authRouter = routers[2]; + var relativePath = nconf.get('relative_path'); + var ensureLoggedIn = require('connect-ensure-login'); if (Array.isArray(hotswapIds) && hotswapIds.length) { for(var idx,x=0;x Date: Wed, 24 Aug 2016 14:52:18 +0300 Subject: [PATCH 099/386] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1f452613a3..25cc29885d 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.25", - "nodebb-theme-vanilla": "5.1.13", + "nodebb-theme-persona": "4.1.26", + "nodebb-theme-vanilla": "5.1.14", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From 6a04e2909dacf5f0932f4d65f48cb9cea2c23d56 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 24 Aug 2016 14:54:18 +0300 Subject: [PATCH 100/386] page param not used --- public/src/client/users.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/public/src/client/users.js b/public/src/client/users.js index 17016fc7b0..8f648e6198 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -45,16 +45,14 @@ define('forum/users', ['translator'], function(translator) { }); } - function doSearch(page) { + function doSearch() { $('[component="user/search/icon"]').removeClass('fa-search').addClass('fa-spinner fa-spin'); var username = $('#search-user').val(); - page = page || 1; - var activeSection = getActiveSection(); var query = { section: activeSection, - page: page + page: 1 }; if (!username) { From 15d9a5f4d59b44a0f4551964b3579dd15d46ef01 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Wed, 24 Aug 2016 09:02:29 -0400 Subject: [PATCH 101/386] Latest translations and fallbacks --- public/language/it/error.json | 14 +++++++------- public/language/it/global.json | 4 ++-- public/language/it/groups.json | 4 ++-- public/language/it/notifications.json | 4 ++-- public/language/it/pages.json | 6 +++--- public/language/it/topic.json | 8 ++++---- public/language/it/user.json | 10 +++++----- public/language/zh_TW/modules.json | 2 +- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/public/language/it/error.json b/public/language/it/error.json index 8fd8822deb..2244b8e8a5 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -24,14 +24,14 @@ "no-email-to-confirm": "Questo forum richiede la conferma dell'indirizzo email, per favore clicca qui per inserirne uno", "email-confirm-failed": "Non possiamo confermare la tua email, per favore prova ancora più tardi.", "confirm-email-already-sent": "Email di conferma già inviata, per favore attendere %1 minuti per richiederne un'altra.", - "sendmail-not-found": "The sendmail executable could not be found, please ensure it is installed and executable by the user running NodeBB.", + "sendmail-not-found": "Non è stato possibile trovare l'eseguibile sendmail, per favore assicurati che sia installato ed eseguibile dall'utente su cui è installato NodeBB.", "username-too-short": "Nome utente troppo corto", "username-too-long": "Nome utente troppo lungo", "password-too-long": "Password troppo lunga", "user-banned": "Utente bannato", "user-too-new": "Devi attendere %1 secondi prima di creare il tuo primo post", "blacklisted-ip": "Purtroppo il tuo indirizzo IP è stato bannato da questa community. Se credi che ci sia stato un errore contatta un amministratore.", - "ban-expiry-missing": "Please provide an end date for this ban", + "ban-expiry-missing": "Per favore fornisci una data finale per questo ban", "no-category": "La Categoria non esiste", "no-topic": "La discussione non esiste", "no-post": "Il Post non esiste", @@ -70,8 +70,8 @@ "still-uploading": "Per favore attendere il completamento degli uploads.", "file-too-big": "La dimensione massima consentita è di %1 kB - si prega di caricare un file più piccolo", "guest-upload-disabled": "Il caricamento da ospite è stato disattivato", - "already-favourited": "You have already bookmarked this post", - "already-unfavourited": "You have already unbookmarked this post", + "already-favourited": "Hai già aggiunto questo post ai favoriti", + "already-unfavourited": "Hai già tolto questo post dai favoriti", "cant-ban-other-admins": "Non puoi bannare altri amministratori!", "cant-remove-last-admin": "Sei l'unico Amministratore. Aggiungi un altro amministratore prima di rimuovere il tuo ruolo", "cant-delete-admin": "Togli i privilegi amministrativi da questo account prima di provare ad eliminarlo.", @@ -117,11 +117,11 @@ "parse-error": "Qualcosa è andato storto durante l'analisi della risposta proveniente dal server", "wrong-login-type-email": "Per favore usa la tua email per accedere", "wrong-login-type-username": "Per favore usa il tuo nome utente per accedere", - "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).", - "no-session-found": "No login session found!", + "invite-maximum-met": "Hai invitato il massimo numero di persone possibili (%1 su %2).", + "no-session-found": "Nessuna sessione valida di login trovata!", "not-in-room": "L'utente non è in questa stanza", "no-users-in-room": "Nessun utente in questa stanza", "cant-kick-self": "Non puoi espellerti dal gruppo", "no-users-selected": "Nessun utente selezionato", - "invalid-home-page-route": "Invalid home page route" + "invalid-home-page-route": "Percorso della pagina iniziale non valido" } \ No newline at end of file diff --git a/public/language/it/global.json b/public/language/it/global.json index ccfff6ad6e..0bebb5cd9e 100644 --- a/public/language/it/global.json +++ b/public/language/it/global.json @@ -51,7 +51,7 @@ "posts": "Post", "best": "Migliore", "upvoters": "Upvoters", - "upvoted": "Upvoted", + "upvoted": "Apprezzati", "downvoters": "Downvoters", "downvoted": "Downvoted", "views": "Visualizzazioni", @@ -92,6 +92,6 @@ "enter_page_number": "Inserisci il numero della pagina", "upload_file": "Carica file", "upload": "Carica", - "allowed-file-types": "Allowed file types are %1", + "allowed-file-types": "Le estensioni permesse dei file sono %1", "unsaved-changes": "Hai delle modifiche non salvate. Sei sicuro che vuoi lasciare la pagina?" } \ No newline at end of file diff --git a/public/language/it/groups.json b/public/language/it/groups.json index 863b4be850..45cc2d894b 100644 --- a/public/language/it/groups.json +++ b/public/language/it/groups.json @@ -24,7 +24,7 @@ "details.has_no_posts": "I membri di questo gruppo non hanno ancora postato.", "details.latest_posts": "Ultimi Post", "details.private": "Privato", - "details.disableJoinRequests": "Disable join requests", + "details.disableJoinRequests": "Disabilita le richieste d'adesione", "details.grant": "Concedi / Rimuovi la Proprietà", "details.kick": "Espelli", "details.owner_options": "Amministratore del Grupo", @@ -41,7 +41,7 @@ "details.hidden": "Nascosto", "details.hidden_help": "Se abilitato, questo gruppo non sarà visibile nella lista dei gruppi e gli utenti dovranno essere invitati manualmente", "details.delete_group": "Elimina il Gruppo", - "details.private_system_help": "Private groups is disabled at system level, this option does not do anything", + "details.private_system_help": "I gruppi privati sono disabilitati dal livello del sistema, questa opzione non fa nulla", "event.updated": "I dettagli del Gruppo sono stati aggiornati", "event.deleted": "Il gruppo \"%1\" è stato eliminato", "membership.accept-invitation": "Accetta l'invito", diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json index c7725575b5..82fa25deec 100644 --- a/public/language/it/notifications.json +++ b/public/language/it/notifications.json @@ -17,8 +17,8 @@ "moved_your_post": "%1 ha spostato il tuo post su %2", "moved_your_topic": "%1 è stato spostato %2", "user_flagged_post_in": "%1 ha segnalato un post in %2", - "user_flagged_post_in_dual": "%1 and %2 flagged a post in %3", - "user_flagged_post_in_multiple": "%1 and %2 others flagged a post in %3", + "user_flagged_post_in_dual": "%1 e %2 hanno segnalato un post in %3", + "user_flagged_post_in_multiple": "%1 ed altri %2 hanno segnalato un post in %3", "user_posted_to": "%1 ha postato una risposta a: %2", "user_posted_to_dual": "%1 e %2 hanno postato una risposta su: %3", "user_posted_to_multiple": "%1 ed altri %2 hanno postato una risposta su: %3", diff --git a/public/language/it/pages.json b/public/language/it/pages.json index e509b23ef8..b0920945fc 100644 --- a/public/language/it/pages.json +++ b/public/language/it/pages.json @@ -6,13 +6,13 @@ "popular-month": "Discussioni popolari questo mese", "popular-alltime": "Discussioni più popolari di sempre", "recent": "Discussioni Recenti", - "flagged-posts": "Flagged Posts", + "flagged-posts": "Post Segnalati", "users/online": "Utenti Online", "users/latest": "Ultimi Utenti", "users/sort-posts": "Utenti maggiori contributori", "users/sort-reputation": "Utenti con la reputazione più alta", "users/banned": "Utenti Bannati", - "users/most-flags": "Most flagged users", + "users/most-flags": "Gli utenti più segnalati", "users/search": "Ricerca Utenti", "notifications": "Notifiche", "tags": "Tags", @@ -29,7 +29,7 @@ "account/edit/password": "Modificando la password di \"%1\"", "account/edit/username": "Modificando il nome utente di \"%1\"", "account/edit/email": "Modificando l'email di \"%1\"", - "account/info": "Account Info", + "account/info": "Informazioni dell'account", "account/following": "Persone seguite da %1", "account/followers": "Persone che seguono %1", "account/posts": "Post creati da %1", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index 5367678fa8..4ff8c7cdc9 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -47,7 +47,7 @@ "watching": "Seguito", "not-watching": "Non Seguito", "ignoring": "Ignorato", - "watching.description": "Notify me of new replies.
    Show topic in unread.", + "watching.description": "Notificami sulle nuove risposte.
    Mostra la discussione tra le non lette.", "not-watching.description": "Non notificarmi sulle nuove risposte.
    Mostra la discussione fra le non lette se la categoria non è ignorata.", "ignoring.description": "Non notificarmi sulle nuove risposte.
    Non mostrare la discussione fra le non lette.", "thread_tools.title": "Strumenti per la Discussione", @@ -74,9 +74,9 @@ "disabled_categories_note": "Le Categorie disabilitate sono in grigio", "confirm_move": "Sposta", "confirm_fork": "Dividi", - "favourite": "Bookmark", - "favourites": "Bookmarks", - "favourites.has_no_favourites": "You haven't bookmarked any posts yet.", + "favourite": "Favorito", + "favourites": "Favoriti", + "favourites.has_no_favourites": "Non hai ancora aggiunto nessun post tra i favoriti", "loading_more_posts": "Caricamento altri post", "move_topic": "Sposta Discussione", "move_topics": "Sposta Discussioni", diff --git a/public/language/it/user.json b/public/language/it/user.json index 939f0e0923..355e6e84be 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -80,7 +80,7 @@ "has_no_posts": "Questo utente non ha ancora scritto niente.", "has_no_topics": "Questo utente non ha ancora avviato discussioni.", "has_no_watched_topics": "Questo utente non sta osservando discussioni.", - "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.", + "has_no_upvoted_posts": "Questo utente non ha ancora apprezzato nessun post.", "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.", "has_no_voted_posts": "Questo utente non ha post votati", "email_hidden": "Email Nascosta", @@ -104,13 +104,13 @@ "select-homepage": "Seleziona una Pagina Iniziale", "homepage": "Pagina iniziale", "homepage_description": "Seleziona una pagina da usare come pagina iniziale o \"Nessuna\" per usare quella di default.", - "custom_route": "Custom Homepage Route", - "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")", + "custom_route": "Percorso della Homepage personalizzato", + "custom_route_help": "Inserisci qui un nome percorso, senza nessuno slash precedente (p.es. \"recent\", o \"popular\")", "sso.title": "Servizi Single-Sign-On", "sso.associated": "Associa con", "sso.not-associated": "Clicca qui per associare con", - "info.latest-flags": "Latest Flags", - "info.no-flags": "No Flagged Posts Found", + "info.latest-flags": "Ultime Segnalazioni", + "info.no-flags": "Non è stato trovato nessun post segnalato", "info.ban-history": "Storico dei Ban recenti", "info.no-ban-history": "Questo utente non è mai stato bannato", "info.banned-until": "Bannato fino %1", diff --git a/public/language/zh_TW/modules.json b/public/language/zh_TW/modules.json index e02c0908f1..61d9b00cea 100644 --- a/public/language/zh_TW/modules.json +++ b/public/language/zh_TW/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "圖片", "composer.upload-picture": "上傳圖片", "composer.upload-file": "上傳檔案", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "禪(Zen)模式", "bootbox.ok": "好", "bootbox.cancel": "取消", "bootbox.confirm": "確認", From 358deb386a555a2d65d9b94f6e963da1f232f981 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 24 Aug 2016 11:14:15 -0400 Subject: [PATCH 102/386] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 25cc29885d..a319e90b61 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.26", - "nodebb-theme-vanilla": "5.1.14", + "nodebb-theme-persona": "4.1.27", + "nodebb-theme-vanilla": "5.1.15", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From f0c4c5979341089817dc3af28427732cdaccc0e3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 24 Aug 2016 16:12:00 -0400 Subject: [PATCH 103/386] up composer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a319e90b61..93b1c4cbf1 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.1.8", + "nodebb-plugin-composer-default": "4.1.9", "nodebb-plugin-dbsearch": "1.0.2", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", From ed3bc06dee1b8fa8ce37100c9b7ab3c90a1ecf97 Mon Sep 17 00:00:00 2001 From: Timothy Fike Date: Wed, 24 Aug 2016 17:48:04 -0400 Subject: [PATCH 104/386] Fix requiring files outside of node_modules e.g. ``` "scripts": [ "../../public/vendor/jquery/js/jquery-ui-1.10.4.custom.js" ] ``` --- src/plugins/load.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/load.js b/src/plugins/load.js index 99019e371f..8eac293829 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -276,7 +276,8 @@ module.exports = function(Plugins) { * With npm@3, dependencies can become flattened, and appear at the root level. * This method resolves these differences if it can. */ - var atRootLevel = fullPath.match(/node_modules/g).length === 1; + var matches = fullPath.match(/node_modules/g); + var atRootLevel = !matches || matches.length === 1; try { fs.statSync(fullPath); @@ -329,4 +330,4 @@ module.exports = function(Plugins) { } }); }; -}; \ No newline at end of file +}; From 9eb64aa3fa718b750e6a8826628515a276fbc4ce Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 10:30:46 +0300 Subject: [PATCH 105/386] closes #4975 --- public/src/ajaxify.js | 6 +---- public/src/client/account/edit.js | 38 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index d0592d8511..bec556c07a 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -216,11 +216,7 @@ $(document).ready(function() { return url; }; - ajaxify.refresh = function(e, callback) { - if (e && e instanceof jQuery.Event) { - e.preventDefault(); - } - + ajaxify.refresh = function(callback) { ajaxify.go(ajaxify.currentPage + window.location.search + window.location.hash, callback, true); }; diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js index db600a2f55..6cbea282d0 100644 --- a/public/src/client/account/edit.js +++ b/public/src/client/account/edit.js @@ -2,12 +2,10 @@ /* globals define, ajaxify, socket, app, config, templates, bootbox */ -define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function(header, uploader, translator) { - var AccountEdit = {}, - uploadedPicture = ''; +define('forum/account/edit', ['forum/account/header', 'uploader', 'translator', 'components'], function(header, uploader, translator, components) { + var AccountEdit = {}; AccountEdit.init = function() { - uploadedPicture = ajaxify.data.uploadedpicture; header.init(); @@ -59,17 +57,15 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], } function updateHeader(picture) { - require(['components'], function(components) { - if (parseInt(ajaxify.data.theirid, 10) !== parseInt(ajaxify.data.yourid, 10)) { - return; - } + if (parseInt(ajaxify.data.theirid, 10) !== parseInt(ajaxify.data.yourid, 10)) { + return; + } - components.get('header/userpicture')[picture ? 'show' : 'hide'](); - components.get('header/usericon')[!picture ? 'show' : 'hide'](); - if (picture) { - components.get('header/userpicture').attr('src', picture); - } - }); + components.get('header/userpicture')[picture ? 'show' : 'hide'](); + components.get('header/usericon')[!picture ? 'show' : 'hide'](); + if (picture) { + components.get('header/userpicture').attr('src', picture); + } } function handleImageChange() { @@ -137,8 +133,8 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], } function saveSelection() { - var type = modal.find('.list-group-item.active').attr('data-type'), - src = modal.find('.list-group-item.active img').attr('src'); + var type = modal.find('.list-group-item.active').attr('data-type'); + changeUserPicture(type, function(err) { if (err) { return app.alertError(err.message); @@ -176,7 +172,7 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], if (err) { return app.alertError(err.message); } - + window.location.href = config.relative_path + '/'; }); } @@ -192,15 +188,17 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function handleImageUpload(modal) { function onUploadComplete(urlOnServer) { - urlOnServer = urlOnServer + '?' + new Date().getTime(); + urlOnServer = urlOnServer + '?' + Date.now(); updateHeader(urlOnServer); if (ajaxify.data.picture.length) { $('#user-current-picture, img.avatar').attr('src', urlOnServer); - uploadedPicture = urlOnServer; + ajaxify.data.uploadedpicture = urlOnServer; } else { - ajaxify.refresh(); + ajaxify.refresh(function() { + $('#user-current-picture, img.avatar').attr('src', urlOnServer); + }); } } From b6ac809a7dbb89f8cd1bbcb78c59f5ba3f342457 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 13:56:43 +0300 Subject: [PATCH 106/386] moved sync code out of async --- src/user/picture.js | 49 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/user/picture.js b/src/user/picture.js index 78ad131ab4..fcac5c5c6f 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -1,20 +1,20 @@ 'use strict'; -var async = require('async'), - path = require('path'), - fs = require('fs'), - os = require('os'), - nconf = require('nconf'), - crypto = require('crypto'), - winston = require('winston'), - request = require('request'), - mime = require('mime'), +var async = require('async'); +var path = require('path'); +var fs = require('fs'); +var os = require('os'); +var nconf = require('nconf'); +var crypto = require('crypto'); +var winston = require('winston'); +var request = require('request'); +var mime = require('mime'); - plugins = require('../plugins'), - file = require('../file'), - image = require('../image'), - meta = require('../meta'), - db = require('../database'); +var plugins = require('../plugins'); +var file = require('../file'); +var image = require('../image'); +var meta = require('../meta'); +var db = require('../database'); module.exports = function(User) { @@ -28,16 +28,19 @@ module.exports = function(User) { var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; var uploadedImage; + if (parseInt(meta.config.allowProfileImageUploads) !== 1) { + return callback(new Error('[[error:profile-image-uploads-disabled]]')); + } + + if (picture.size > uploadSize * 1024) { + return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); + } + + if (!extension) { + return callback(new Error('[[error:invalid-image-extension]]')); + } + async.waterfall([ - function(next) { - next(parseInt(meta.config.allowProfileImageUploads) !== 1 ? new Error('[[error:profile-image-uploads-disabled]]') : null); - }, - function(next) { - next(picture.size > uploadSize * 1024 ? new Error('[[error:file-too-big, ' + uploadSize + ']]') : null); - }, - function(next) { - next(!extension ? new Error('[[error:invalid-image-extension]]') : null); - }, function(next) { if (plugins.hasListeners('filter:uploadImage')) { return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next); From abd0a1091800ca4823519feae747ee3137bec53c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 14:24:17 +0300 Subject: [PATCH 107/386] added selected group to profile api --- src/controllers/accounts/profile.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index 74e919d8f5..2f257def2c 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -119,6 +119,10 @@ profileController.get = function(req, res, callback) { ); } + userData.selectedGroup = userData.groups.find(function(group) { + return group && group.name === userData.groupTitle; + }); + plugins.fireHook('filter:user.account', {userData: userData, uid: req.uid}, next); } ], function(err, results) { From 5393a98e2630c35718ef718f06f2c7ec4c1f4f1e Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Thu, 25 Aug 2016 09:02:33 -0400 Subject: [PATCH 108/386] Latest translations and fallbacks --- public/language/gl/category.json | 4 ++-- public/language/gl/error.json | 12 ++++++------ public/language/gl/notifications.json | 2 +- public/language/it/error.json | 2 +- public/language/it/topic.json | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/public/language/gl/category.json b/public/language/gl/category.json index eb12b9660e..aeafc9f8e0 100644 --- a/public/language/gl/category.json +++ b/public/language/gl/category.json @@ -14,7 +14,7 @@ "ignoring": "Ignorando", "watching.description": "Amosa-los temas en \"non lidos\"", "ignoring.description": "Non amosa-los temas en \"non lidos\"", - "watch.message": "Agora Sigues as novidades desta categoría", - "ignore.message": "Agora ignoras as novidades nesta categoría", + "watch.message": "Agora segues as novidades desta categoría.", + "ignore.message": "Agora ignoras as novidades nesta categoría.", "watched-categories": "Categorías vixiadas" } \ No newline at end of file diff --git a/public/language/gl/error.json b/public/language/gl/error.json index d7cf7fa7da..d372ec2b2e 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -2,13 +2,13 @@ "invalid-data": "Datos non válidos", "not-logged-in": "Parece que estás desconectado.", "account-locked": "A túa conta foi bloqueada temporalmente.", - "search-requires-login": "As buscas requiren unha conta - por favor inicia sesión ou rexístrate.", + "search-requires-login": "As buscas requiren unha conta. Por favor inicia sesión ou rexístrate.", "invalid-cid": "Identificador de Categoría Inválido ", "invalid-tid": "Identificador de Tema Inválido", "invalid-pid": "Identificador de Publicación Inválido", "invalid-uid": "Identificador de Usuario Inválido", "invalid-username": "Nome de Usuario Inválido", - "invalid-email": "Correo electrónico inválido", + "invalid-email": "Enderezo electrónico inválido", "invalid-title": "Título inválido!", "invalid-user-data": "Datos de Usuario Inválidos", "invalid-password": "Contrasinal Inválido", @@ -17,12 +17,12 @@ "csrf-invalid": "Non fomos capaces de entrar, probablemente debido a que a sesión expirou. Por favor, téntao de novo", "invalid-pagination-value": "Valor de paxinación incorreto, ten que estar entre %1 e %2", "username-taken": "Nome de usuario en uso", - "email-taken": "Correo en uso", + "email-taken": "Enderezo electrónico en uso", "email-not-confirmed": "O teu correo aínda non está confirmado, por favor pica aquí para confirmalo.", "email-not-confirmed-chat": "Non podes charlar ata que confirmes o teu correo, por favor pica aquí para confirmalo.", - "email-not-confirmed-email-sent": "O teu correo electrónico está sen confirmar. Por favor, busca o correo de confirmación na túa bandexa de entrada.", - "no-email-to-confirm": "Este foro require confirmación de correo, por favor pica aquí para introducir un correo.", - "email-confirm-failed": "Non podemos confirmar o teu correo, por favor téntao de novo máis tarde.", + "email-not-confirmed-email-sent": "O teu enderezo electrónico está sen confirmar. Por favor, busca o correo de confirmación na túa bandexa de entrada.", + "no-email-to-confirm": "O foro require confirmación de enderezo electrónico, por favor pica aquí para engadir un.", + "email-confirm-failed": "Non podemos confirmar o teu enderezo, por favor téntao de novo máis tarde.", "confirm-email-already-sent": "O correo de confirmación foi enviado, agarda %1 minute(s) para enviar outro.", "sendmail-not-found": "Non se atopa o executable \"sendmail\", por favor, asegúrate de que está instalado no teu sistema e que é accesible polo usuario que executa NodeBB. ", "username-too-short": "Nome de usuario demasiado curto", diff --git a/public/language/gl/notifications.json b/public/language/gl/notifications.json index deeb2835a5..326680a2fd 100644 --- a/public/language/gl/notifications.json +++ b/public/language/gl/notifications.json @@ -15,7 +15,7 @@ "upvoted_your_post_in_dual": "%1 e %2 votaron positivamente a túa mensaxe en %3.", "upvoted_your_post_in_multiple": "%1 e %2 máis votaron positivamente a túa mensaxe en %3.", "moved_your_post": "%1 moveu a túa publicación a%2", - "moved_your_topic": "%1 foi movido %2", + "moved_your_topic": "%1 moveu %2", "user_flagged_post_in": "%1 reportou unha mensaxe en %2", "user_flagged_post_in_dual": "%1 e %2 reportaron a túa mensaxe en %3", "user_flagged_post_in_multiple": "%1 e outras %2 persoas reportaron unha mensaxe en %3", diff --git a/public/language/it/error.json b/public/language/it/error.json index 2244b8e8a5..4412fbc484 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -89,7 +89,7 @@ "group-already-requested": "La tua richiesta di partecipazione è già stata inviata", "post-already-deleted": "Questo Post è già stato cancellato", "post-already-restored": "Questo Post è già stato ripristinato", - "topic-already-deleted": "Questo Topic è già stato cancellato", + "topic-already-deleted": "Questo topic è già stato eliminato", "topic-already-restored": "Questo Topic è già stato ripristinato", "cant-purge-main-post": "Non puoi svuotare il primo messaggio, elimina invece l'intera discussione", "topic-thumbnails-are-disabled": "Le anteprime della Discussione sono disabilitate.", diff --git a/public/language/it/topic.json b/public/language/it/topic.json index 4ff8c7cdc9..f44a429712 100644 --- a/public/language/it/topic.json +++ b/public/language/it/topic.json @@ -5,7 +5,7 @@ "no_topics_found": "Nessuna discussione trovata!", "no_posts_found": "Nessun post trovato!", "post_is_deleted": "Questo post è eliminato!", - "topic_is_deleted": "Questa discussione é stata eliminata", + "topic_is_deleted": "Questa discussione è stata eliminata", "profile": "Profilo", "posted_by": "Pubblicato da: %1", "posted_by_guest": "Scritto da Ospite", From 276031cd6ff554a98287c5eacf58a6f8ddce7a51 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 16:05:02 +0300 Subject: [PATCH 109/386] closes #4585, closes https://github.com/NodeBB/nodebb-theme-persona/issues/299 --- public/src/app.js | 4 ++-- public/src/modules/chat.js | 5 ++--- src/messaging.js | 30 ++++++++++++++++-------------- src/messaging/rooms.js | 18 ++++++++++++++++++ 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 66257e7980..e66d0d03c2 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -189,12 +189,12 @@ app.cacheBuster = null; } } - app.createUserTooltips = function(els) { + app.createUserTooltips = function(els, placement) { els = els || $('body'); els.find('.avatar,img[title].teaser-pic,img[title].user-img,div.user-icon,span.user-icon').each(function() { if (!utils.isTouchDevice()) { $(this).tooltip({ - placement: 'top', + placement: placement || $(this).attr('title-placement') || 'top', title: $(this).attr('title') }); } diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 445b4212e4..8e0b435434 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -113,13 +113,12 @@ define('chat', [ return room.teaser; }); - chatsListEl.empty(); - templates.parse('partials/chat_dropdown', { rooms: rooms }, function(html) { translator.translate(html, function(translated) { - chatsListEl.html(translated); + chatsListEl.empty().html(translated); + app.createUserTooltips(chatsListEl, 'right'); }); }); }); diff --git a/src/messaging.js b/src/messaging.js index 1ce4160d62..b17bea6ec2 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -242,6 +242,9 @@ var async = require('async'), } async.parallel({ + roomData: function(next) { + Messaging.getRoomsData(roomIds, next); + }, unread: function(next) { db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next); }, @@ -254,7 +257,7 @@ var async = require('async'), uids = uids.filter(function(value) { return value && parseInt(value, 10) !== parseInt(uid, 10); }); - user.getUsersFields(uids, ['uid', 'username', 'picture', 'status', 'lastonline'] , next); + user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); }); }, next); }, @@ -267,29 +270,28 @@ var async = require('async'), if (err) { return callback(err); } - var rooms = results.users.map(function(users, index) { - var data = { - users: users, - unread: results.unread[index], - roomId: roomIds[index], - teaser: results.teasers[index] - }; - data.users.forEach(function(userData) { + + results.roomData.forEach(function(room, index) { + room.users = results.users[index]; + room.unread = results.unread[index]; + room.teaser = results.teasers[index]; + + room.users.forEach(function(userData) { if (userData && parseInt(userData.uid, 10)) { userData.status = user.getStatus(userData); } }); - data.users = data.users.filter(function(user) { + room.users = room.users.filter(function(user) { return user && parseInt(user.uid, 10); }); - data.lastUser = data.users[0]; - data.usernames = data.users.map(function(user) { + room.lastUser = room.users[0]; + + room.usernames = room.users.map(function(user) { return user.username; }).join(', '); - return data; }); - callback(null, {rooms: rooms, nextStart: stop + 1}); + callback(null, {rooms: results.roomData, nextStart: stop + 1}); }); }); }; diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js index 0c9c5bb01f..8c021d509e 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -21,6 +21,24 @@ module.exports = function(Messaging) { }); }; + Messaging.getRoomsData = function(roomIds, callback) { + var keys = roomIds.map(function(roomId) { + return 'chat:room:' + roomId; + }); + db.getObjects(keys, function(err, roomData) { + if (err) { + return callback(err); + } + roomData.forEach(function(data) { + if (data) { + data.roomName = data.roomName || '[[modules:chat.roomname, ' + data.roomId + ']]'; + data.roomName = validator.escape(String(data.roomName)); + } + }); + callback(null, roomData); + }); + }; + Messaging.newRoom = function(uid, toUids, callback) { var roomId; var now = Date.now(); From bb6a3f3a522b4e28b89681a8b80ce9adcf4eeb25 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 16:26:54 +0300 Subject: [PATCH 110/386] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 93b1c4cbf1..d5cfbd0450 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.27", - "nodebb-theme-vanilla": "5.1.15", + "nodebb-theme-persona": "4.1.28", + "nodebb-theme-vanilla": "5.1.16", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From 59832358758c4a2538f2481e5670f8c33a369253 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 16:40:18 +0300 Subject: [PATCH 111/386] .translateAttr --- public/src/overrides.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/src/overrides.js b/public/src/overrides.js index 62eca1d568..49dce7e754 100644 --- a/public/src/overrides.js +++ b/public/src/overrides.js @@ -68,6 +68,15 @@ if ('undefined' !== typeof window) { return translate(this, 'val', str); }; + $.fn.translateAttr = function(attr, str) { + return this.each(function() { + var el = $(this); + translator.translate(str, function(translated) { + el.attr(attr, translated); + }); + }); + }; + function translate(elements, type, str) { return elements.each(function() { var el = $(this); @@ -107,7 +116,7 @@ if ('undefined' !== typeof window) { var dialog = bootbox.dialog, prompt = bootbox.prompt, confirm = bootbox.confirm; - + function translate(modal) { var header = modal.find('.modal-header'), footer = modal.find('.modal-footer'); From ad0dd3bf289223b8d28ff034924dfedb4598ac67 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 16:56:13 +0300 Subject: [PATCH 112/386] up theme --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5cfbd0450..77fd981b9d 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.28", + "nodebb-theme-persona": "4.1.29", "nodebb-theme-vanilla": "5.1.16", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", From f996dfab9130a18443cd59e2602bc2bf3c6647ba Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 17:02:22 +0300 Subject: [PATCH 113/386] removed find --- src/controllers/accounts/profile.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js index 2f257def2c..de10c69b6a 100644 --- a/src/controllers/accounts/profile.js +++ b/src/controllers/accounts/profile.js @@ -119,8 +119,10 @@ profileController.get = function(req, res, callback) { ); } - userData.selectedGroup = userData.groups.find(function(group) { - return group && group.name === userData.groupTitle; + userData.groups.forEach(function(group) { + if (group && group.name === userData.groupTitle) { + userData.selectedGroup = group; + } }); plugins.fireHook('filter:user.account', {userData: userData, uid: req.uid}, next); From 6c8a34ae50ab02212ca43dad0e09e5bcc24518d1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 17:32:26 +0300 Subject: [PATCH 114/386] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77fd981b9d..f795174f97 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.29", + "nodebb-theme-persona": "4.1.30", "nodebb-theme-vanilla": "5.1.16", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", From da47f5675cc4b5990c5a6a7a4649b95b1510c272 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Aug 2016 13:00:59 -0400 Subject: [PATCH 115/386] don't wrap images in links if the link is blank, fixes #4976 --- public/src/client/topic/posts.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 656364b3a1..c6dfaaaaa6 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -335,6 +335,10 @@ define('forum/topic/posts', [ src = $this.attr('src'), suffixRegex = /-resized(\.[\w]+)?$/; + if (src === 'about:blank') { + return; + } + if (utils.isRelativeUrl(src) && suffixRegex.test(src)) { src = src.replace(suffixRegex, '$1'); } From 4dd5d1877f11e227fad2f0ba707cb5e33129d546 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Aug 2016 14:11:22 -0400 Subject: [PATCH 116/386] update themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f795174f97..99a0a2354e 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.30", - "nodebb-theme-vanilla": "5.1.16", + "nodebb-theme-persona": "4.1.31", + "nodebb-theme-vanilla": "5.1.17", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From aa45db53f955d2240a50bffc6dbc97890f1b2f71 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Aug 2016 14:23:53 -0400 Subject: [PATCH 117/386] update persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 99a0a2354e..c4d3a5d945 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.31", + "nodebb-theme-persona": "4.1.32", "nodebb-theme-vanilla": "5.1.17", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", From f7540a7edca502eb103b5ce55550ca9a26ac15fe Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 21:56:13 +0300 Subject: [PATCH 118/386] closes #4811 --- public/language/en_GB/global.json | 3 ++- public/src/ajaxify.js | 3 ++- public/src/sockets.js | 6 +++++- src/middleware/header.js | 6 +++++- src/views/500-embed.tpl | 5 ++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index b8cadf0bc6..66ea4af420 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -120,5 +120,6 @@ "upload": "Upload", "allowed-file-types": "Allowed file types are %1", - "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?", + "reconnecting-message": "Looks like your connection to %1 was lost, please wait while we try to reconnect." } diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index bec556c07a..8237a9961b 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -375,7 +375,8 @@ $(document).ready(function() { app.load(); $('[data-template]').each(function() { - templates.cache[$(this).attr('data-template')] = $(this).html(); + templates.cache[$(this).attr('data-template')] = $('
    ').html($(this).html()).text(); + $(this).parent().remove(); }); }); \ No newline at end of file diff --git a/public/src/sockets.js b/public/src/sockets.js index f2a9bfd346..15eee26b5e 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -48,9 +48,11 @@ app.isConnected = false; if (reconnecting) { var reconnectEl = $('#reconnect'); + var reconnectAlert = $('#reconnect-alert'); reconnectEl.tooltip('destroy'); reconnectEl.html(''); + reconnectAlert.fadeOut(500); reconnecting = false; reJoinCurrentRoom(); @@ -102,12 +104,14 @@ app.isConnected = false; function onReconnecting() { reconnecting = true; var reconnectEl = $('#reconnect'); + var reconnectAlert = $('#reconnect-alert'); if (!reconnectEl.hasClass('active')) { reconnectEl.html(''); + reconnectAlert.fadeIn(500).removeClass('hide'); } - reconnectEl.addClass('active').removeClass("hide").tooltip({ + reconnectEl.addClass('active').removeClass('hide').tooltip({ placement: 'bottom' }); } diff --git a/src/middleware/header.js b/src/middleware/header.js index 4f25a55ca5..6425c17d74 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -2,6 +2,7 @@ var async = require('async'); var nconf = require('nconf'); +var validator = require('validator'); var db = require('../database'); var user = require('../user'); @@ -27,7 +28,10 @@ module.exports = function(app, middleware) { controllers.api.getConfig(req, res, next); }, footer: function(next) { - app.render('footer', {loggedIn: (req.user ? parseInt(req.user.uid, 10) !== 0 : false)}, next); + app.render('footer', { + loggedIn: !!req.uid, + title: validator.escape(meta.config.title || meta.config.browserTitle || 'NodeBB') + }, next); }, plugins: function(next) { plugins.fireHook('filter:middleware.buildHeader', {req: req, locals: res.locals}, next); diff --git a/src/views/500-embed.tpl b/src/views/500-embed.tpl index 537cbac136..4d4dd6fc34 100644 --- a/src/views/500-embed.tpl +++ b/src/views/500-embed.tpl @@ -2,8 +2,7 @@
    [[global:500.title]]

    [[global:500.message]]

    -

    {path}

    -

    {error}

    - +

    {path}

    + <!-- IF error -->

    {error}

    <!-- ENDIF error -->
    \ No newline at end of file From 056942ea4d2f16eb7f706579fc8485c8bac51f5b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 22:01:56 +0300 Subject: [PATCH 119/386] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c4d3a5d945..b498b13924 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.32", - "nodebb-theme-vanilla": "5.1.17", + "nodebb-theme-persona": "4.1.33", + "nodebb-theme-vanilla": "5.1.18", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From 254428726902628be1014443cbd5263212d975cb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 25 Aug 2016 22:16:54 +0300 Subject: [PATCH 120/386] closes #4977 --- public/less/admin/general/navigation.less | 3 ++- public/src/admin/general/navigation.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/less/admin/general/navigation.less b/public/less/admin/general/navigation.less index e56f4cc409..86fd3cae62 100644 --- a/public/less/admin/general/navigation.less +++ b/public/less/admin/general/navigation.less @@ -1,7 +1,8 @@ #navigation { #active-navigation { width: 100%; - + min-height: 50px; + border: 1px solid #eee; .active { background-color: #eee; } diff --git a/public/src/admin/general/navigation.js b/public/src/admin/general/navigation.js index 94fbe01549..9ebdce555a 100644 --- a/public/src/admin/general/navigation.js +++ b/public/src/admin/general/navigation.js @@ -66,7 +66,7 @@ define('admin/general/navigation', ['translator', 'iconSelect'], function(transl data = id === 'custom' ? {iconClass: 'fa-navicon'} : available[id]; data.enabled = false; - data.index = parseInt($('#enabled').children().last().attr('data-index'), 10) + 1; + data.index = (parseInt($('#enabled').children().last().attr('data-index'), 10) || 0) + 1; templates.parse('admin/general/navigation', 'navigation', {navigation: [data]}, function(li) { li = $(translator.unescape(li)); From 2428d5e44217e77abefbf4af9763e4251a1e5d8d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Aug 2016 16:10:06 -0400 Subject: [PATCH 121/386] Update bootstrap in ACP Also fixed #4872 by moving user creation modal to root level (outside of .panel) --- public/less/admin/bootstrap/bootstrap.less | 4 +- .../less/admin/bootstrap/button-groups.less | 2 +- public/less/admin/bootstrap/forms.less | 2 +- public/less/admin/bootstrap/input-groups.less | 2 +- .../admin/bootstrap/mixins/tab-focus.less | 6 +- public/less/admin/bootstrap/panels.less | 2 +- public/less/admin/bootstrap/scaffolding.less | 2 +- public/less/admin/bootstrap/theme.less | 4 +- public/less/admin/bootstrap/variables.less | 184 +++++++++--------- public/src/admin/manage/users.js | 75 ++++--- src/meta/css.js | 3 +- src/views/admin/manage/users.tpl | 40 ---- .../admin/partials/create_user_modal.tpl | 21 ++ 13 files changed, 174 insertions(+), 173 deletions(-) create mode 100644 src/views/admin/partials/create_user_modal.tpl diff --git a/public/less/admin/bootstrap/bootstrap.less b/public/less/admin/bootstrap/bootstrap.less index 1c0477805f..f0aa08f3a6 100644 --- a/public/less/admin/bootstrap/bootstrap.less +++ b/public/less/admin/bootstrap/bootstrap.less @@ -1,6 +1,6 @@ /*! - * Bootstrap v3.3.6 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ diff --git a/public/less/admin/bootstrap/button-groups.less b/public/less/admin/bootstrap/button-groups.less index 293245a650..16db0c6135 100644 --- a/public/less/admin/bootstrap/button-groups.less +++ b/public/less/admin/bootstrap/button-groups.less @@ -59,7 +59,7 @@ .border-right-radius(0); } } -// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it .btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { .border-left-radius(0); diff --git a/public/less/admin/bootstrap/forms.less b/public/less/admin/bootstrap/forms.less index e8b071a138..9377d3846b 100644 --- a/public/less/admin/bootstrap/forms.less +++ b/public/less/admin/bootstrap/forms.less @@ -181,7 +181,7 @@ input[type="search"] { // set a pixel line-height that matches the given height of the input, but only // for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848 // -// Note that as of 8.3, iOS doesn't support `datetime` or `week`. +// Note that as of 9.3, iOS doesn't support `week`. @media screen and (-webkit-min-device-pixel-ratio: 0) { input[type="date"], diff --git a/public/less/admin/bootstrap/input-groups.less b/public/less/admin/bootstrap/input-groups.less index 5f73eec40c..d0763db7ff 100644 --- a/public/less/admin/bootstrap/input-groups.less +++ b/public/less/admin/bootstrap/input-groups.less @@ -29,7 +29,7 @@ width: 100%; margin-bottom: 0; - + &:focus { z-index: 3; } diff --git a/public/less/admin/bootstrap/mixins/tab-focus.less b/public/less/admin/bootstrap/mixins/tab-focus.less index 1f1f05ab05..d12d23629f 100644 --- a/public/less/admin/bootstrap/mixins/tab-focus.less +++ b/public/less/admin/bootstrap/mixins/tab-focus.less @@ -1,9 +1,9 @@ // WebKit-style focus .tab-focus() { - // Default - outline: thin dotted; - // WebKit + // WebKit-specific. Other browsers will keep their default outline style. + // (Initially tried to also force default via `outline: initial`, + // but that seems to erroneously remove the outline in Firefox altogether.) outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } diff --git a/public/less/admin/bootstrap/panels.less b/public/less/admin/bootstrap/panels.less index 425eb5e642..65aa3a83f3 100644 --- a/public/less/admin/bootstrap/panels.less +++ b/public/less/admin/bootstrap/panels.less @@ -214,7 +214,7 @@ } -// Collapsable panels (aka, accordion) +// Collapsible panels (aka, accordion) // // Wrap a series of panels in `.panel-group` to turn them into an accordion with // the help of our collapse JavaScript plugin. diff --git a/public/less/admin/bootstrap/scaffolding.less b/public/less/admin/bootstrap/scaffolding.less index 1929bfc5cf..64a29c6a5e 100644 --- a/public/less/admin/bootstrap/scaffolding.less +++ b/public/less/admin/bootstrap/scaffolding.less @@ -120,7 +120,7 @@ hr { // Only display content to screen readers // -// See: http://a11yproject.com/posts/how-to-hide-content/ +// See: http://a11yproject.com/posts/how-to-hide-content .sr-only { position: absolute; diff --git a/public/less/admin/bootstrap/theme.less b/public/less/admin/bootstrap/theme.less index 8f51d913dc..fb6174427b 100644 --- a/public/less/admin/bootstrap/theme.less +++ b/public/less/admin/bootstrap/theme.less @@ -1,6 +1,6 @@ /*! - * Bootstrap v3.3.6 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ diff --git a/public/less/admin/bootstrap/variables.less b/public/less/admin/bootstrap/variables.less index d0cf54f043..03b54980ae 100644 --- a/public/less/admin/bootstrap/variables.less +++ b/public/less/admin/bootstrap/variables.less @@ -1,4 +1,4 @@ -// Paper 3.3.5 +// // Variables // -------------------------------------------------- @@ -9,16 +9,16 @@ @gray-base: #000; @gray-darker: lighten(@gray-base, 13.5%); // #222 -@gray-dark: #212121; -@gray: #666; -@gray-light: #bbb; +@gray-dark: lighten(@gray-base, 20%); // #333 +@gray: lighten(@gray-base, 33.5%); // #555 +@gray-light: lighten(@gray-base, 46.7%); // #777 @gray-lighter: lighten(@gray-base, 93.5%); // #eee -@brand-primary: #2196F3; -@brand-success: #4CAF50; -@brand-info: #9C27B0; -@brand-warning: #ff9800; -@brand-danger: #e51c23; +@brand-primary: darken(#428bca, 6.5%); // #337ab7 +@brand-success: #5cb85c; +@brand-info: #5bc0de; +@brand-warning: #f0ad4e; +@brand-danger: #d9534f; //== Scaffolding @@ -28,7 +28,7 @@ //** Background color for ``. @body-bg: #fff; //** Global text color on ``. -@text-color: @gray; +@text-color: @gray-dark; //** Global textual link color. @link-color: @brand-primary; @@ -42,33 +42,33 @@ // //## Font, line-height, and color for body text, headings, and more. -@font-family-sans-serif: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; @font-family-serif: Georgia, "Times New Roman", Times, serif; //** Default monospace fonts for ``, ``, and `
    `.
     @font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
     @font-family-base:        @font-family-sans-serif;
     
    -@font-size-base:          13px;
    +@font-size-base:          14px;
     @font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
     @font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
     
    -@font-size-h1:            56px;
    -@font-size-h2:            45px;
    -@font-size-h3:            34px;
    -@font-size-h4:            24px;
    -@font-size-h5:            20px;
    -@font-size-h6:            14px;
    +@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
    +@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
    +@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
    +@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
    +@font-size-h5:            @font-size-base;
    +@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
     
     //** Unit-less `line-height` for use in components like buttons.
    -@line-height-base:        1.846; // 20/14
    +@line-height-base:        1.428571429; // 20/14
     //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
     @line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
     
     //** By default, this inherits from the ``.
     @headings-font-family:    inherit;
    -@headings-font-weight:    400;
    +@headings-font-weight:    500;
     @headings-line-height:    1.1;
    -@headings-color:          #444;
    +@headings-color:          inherit;
     
     
     //== Iconography
    @@ -88,7 +88,7 @@
     //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
     
     @padding-base-vertical:     6px;
    -@padding-base-horizontal:   16px;
    +@padding-base-horizontal:   12px;
     
     @padding-large-vertical:    10px;
     @padding-large-horizontal:  16px;
    @@ -102,16 +102,16 @@
     @line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
     @line-height-small:         1.5;
     
    -@border-radius-base:        0px;
    -@border-radius-large:       0px;
    -@border-radius-small:       0px;
    +@border-radius-base:        4px;
    +@border-radius-large:       6px;
    +@border-radius-small:       3px;
     
     //** Global color for active items (e.g., navs or dropdowns).
     @component-active-color:    #fff;
     //** Global background color for active items (e.g., navs or dropdowns).
     @component-active-bg:       @brand-primary;
     
    -//** Width of the `border` for generating carets that indicator dropdowns.
    +//** Width of the `border` for generating carets that indicate dropdowns.
     @caret-width-base:          4px;
     //** Carets increase slightly in size for larger components.
     @caret-width-large:         5px;
    @@ -144,29 +144,29 @@
     
     @btn-font-weight:                normal;
     
    -@btn-default-color:              #444;
    +@btn-default-color:              #333;
     @btn-default-bg:                 #fff;
    -@btn-default-border:             transparent;
    +@btn-default-border:             #ccc;
     
     @btn-primary-color:              #fff;
     @btn-primary-bg:                 @brand-primary;
    -@btn-primary-border:             transparent;
    +@btn-primary-border:             darken(@btn-primary-bg, 5%);
     
     @btn-success-color:              #fff;
     @btn-success-bg:                 @brand-success;
    -@btn-success-border:             transparent;
    +@btn-success-border:             darken(@btn-success-bg, 5%);
     
     @btn-info-color:                 #fff;
     @btn-info-bg:                    @brand-info;
    -@btn-info-border:                transparent;
    +@btn-info-border:                darken(@btn-info-bg, 5%);
     
     @btn-warning-color:              #fff;
     @btn-warning-bg:                 @brand-warning;
    -@btn-warning-border:             transparent;
    +@btn-warning-border:             darken(@btn-warning-bg, 5%);
     
     @btn-danger-color:               #fff;
     @btn-danger-bg:                  @brand-danger;
    -@btn-danger-border:              transparent;
    +@btn-danger-border:              darken(@btn-danger-bg, 5%);
     
     @btn-link-disabled-color:        @gray-light;
     
    @@ -181,14 +181,14 @@
     //##
     
     //** `` background color
    -@input-bg:                       transparent;
    +@input-bg:                       #fff;
     //** `` background color
    -@input-bg-disabled:              transparent;
    +@input-bg-disabled:              @gray-lighter;
     
     //** Text color for ``s
     @input-color:                    @gray;
     //** `` border color
    -@input-border:                   transparent;
    +@input-border:                   #ccc;
     
     // TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
     //** Default `.form-control` border radius
    @@ -203,7 +203,7 @@
     @input-border-focus:             #66afe9;
     
     //** Placeholder text color
    -@input-color-placeholder:        @gray-light;
    +@input-color-placeholder:        #999;
     
     //** Default `.form-control` height
     @input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
    @@ -219,7 +219,7 @@
     @legend-border-color:            #e5e5e5;
     
     //** Background color for textual input addons
    -@input-group-addon-bg:           transparent;
    +@input-group-addon-bg:           @gray-lighter;
     //** Border color for textual input addons
     @input-group-addon-border-color: @input-border;
     
    @@ -241,11 +241,11 @@
     @dropdown-divider-bg:            #e5e5e5;
     
     //** Dropdown link text color.
    -@dropdown-link-color:            @text-color;
    +@dropdown-link-color:            @gray-dark;
     //** Hover color for dropdown links.
     @dropdown-link-hover-color:      darken(@gray-dark, 5%);
     //** Hover background for dropdown links.
    -@dropdown-link-hover-bg:         @gray-lighter;
    +@dropdown-link-hover-bg:         #f5f5f5;
     
     //** Active dropdown menu item text color.
     @dropdown-link-active-color:     @component-active-color;
    @@ -259,7 +259,7 @@
     @dropdown-header-color:          @gray-light;
     
     //** Deprecated `@dropdown-caret-color` as of v3.1.0
    -@dropdown-caret-color:           @gray-light;
    +@dropdown-caret-color:           #000;
     
     
     //-- Z-index master list
    @@ -357,45 +357,45 @@
     //##
     
     // Basics of a navbar
    -@navbar-height:                    64px;
    +@navbar-height:                    50px;
     @navbar-margin-bottom:             @line-height-computed;
     @navbar-border-radius:             @border-radius-base;
     @navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
     @navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
     @navbar-collapse-max-height:       340px;
     
    -@navbar-default-color:             @gray-light;
    -@navbar-default-bg:                #fff;
    -@navbar-default-border:            transparent;
    +@navbar-default-color:             #777;
    +@navbar-default-bg:                #f8f8f8;
    +@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
     
     // Navbar links
    -@navbar-default-link-color:                @gray;
    -@navbar-default-link-hover-color:          @gray-dark;
    +@navbar-default-link-color:                #777;
    +@navbar-default-link-hover-color:          #333;
     @navbar-default-link-hover-bg:             transparent;
    -@navbar-default-link-active-color:         @gray-dark;
    +@navbar-default-link-active-color:         #555;
     @navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
     @navbar-default-link-disabled-color:       #ccc;
     @navbar-default-link-disabled-bg:          transparent;
     
     // Navbar brand label
     @navbar-default-brand-color:               @navbar-default-link-color;
    -@navbar-default-brand-hover-color:         @navbar-default-link-hover-color;
    +@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
     @navbar-default-brand-hover-bg:            transparent;
     
     // Navbar toggle
    -@navbar-default-toggle-hover-bg:           transparent;
    -@navbar-default-toggle-icon-bar-bg:        rgba(0,0,0,0.5);
    -@navbar-default-toggle-border-color:       transparent;
    +@navbar-default-toggle-hover-bg:           #ddd;
    +@navbar-default-toggle-icon-bar-bg:        #888;
    +@navbar-default-toggle-border-color:       #ddd;
     
     
     //=== Inverted navbar
     // Reset inverted navbar basics
    -@navbar-inverse-color:                      @gray-light;
    -@navbar-inverse-bg:                         @brand-primary;
    -@navbar-inverse-border:                     transparent;
    +@navbar-inverse-color:                      lighten(@gray-light, 15%);
    +@navbar-inverse-bg:                         #222;
    +@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
     
     // Inverted navbar links
    -@navbar-inverse-link-color:                 lighten(@brand-primary, 30%);
    +@navbar-inverse-link-color:                 lighten(@gray-light, 15%);
     @navbar-inverse-link-hover-color:           #fff;
     @navbar-inverse-link-hover-bg:              transparent;
     @navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
    @@ -408,10 +408,10 @@
     @navbar-inverse-brand-hover-color:          #fff;
     @navbar-inverse-brand-hover-bg:             transparent;
     
    -// Inverted navbar toggle\
    -@navbar-inverse-toggle-hover-bg:            transparent;
    -@navbar-inverse-toggle-icon-bar-bg:         rgba(0,0,0,0.5);
    -@navbar-inverse-toggle-border-color:        transparent;
    +// Inverted navbar toggle
    +@navbar-inverse-toggle-hover-bg:            #333;
    +@navbar-inverse-toggle-icon-bar-bg:         #fff;
    +@navbar-inverse-toggle-border-color:        #333;
     
     
     //== Navs
    @@ -426,15 +426,15 @@
     @nav-disabled-link-hover-color:             @gray-light;
     
     //== Tabs
    -@nav-tabs-border-color:                     transparent;
    +@nav-tabs-border-color:                     #ddd;
     
     @nav-tabs-link-hover-border-color:          @gray-lighter;
     
    -@nav-tabs-active-link-hover-bg:             transparent;
    +@nav-tabs-active-link-hover-bg:             @body-bg;
     @nav-tabs-active-link-hover-color:          @gray;
    -@nav-tabs-active-link-hover-border-color:   transparent;
    +@nav-tabs-active-link-hover-border-color:   #ddd;
     
    -@nav-tabs-justified-link-border-color:            @nav-tabs-border-color;
    +@nav-tabs-justified-link-border-color:            #ddd;
     @nav-tabs-justified-active-link-border-color:     @body-bg;
     
     //== Pills
    @@ -486,8 +486,8 @@
     
     @jumbotron-padding:              30px;
     @jumbotron-color:                inherit;
    -@jumbotron-bg:                   #f9f9f9;
    -@jumbotron-heading-color:        @headings-color;
    +@jumbotron-bg:                   @gray-lighter;
    +@jumbotron-heading-color:        inherit;
     @jumbotron-font-size:            ceil((@font-size-base * 1.5));
     @jumbotron-heading-font-size:    ceil((@font-size-base * 4.5));
     
    @@ -496,20 +496,20 @@
     //
     //## Define colors for form feedback states and, by default, alerts.
     
    -@state-success-text:             @brand-success;
    +@state-success-text:             #3c763d;
     @state-success-bg:               #dff0d8;
     @state-success-border:           darken(spin(@state-success-bg, -10), 5%);
     
    -@state-info-text:                @brand-info;
    -@state-info-bg:                  #e1bee7;
    +@state-info-text:                #31708f;
    +@state-info-bg:                  #d9edf7;
     @state-info-border:              darken(spin(@state-info-bg, -10), 7%);
     
    -@state-warning-text:             @brand-warning;
    -@state-warning-bg:               #ffe0b2;
    +@state-warning-text:             #8a6d3b;
    +@state-warning-bg:               #fcf8e3;
     @state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
     
    -@state-danger-text:              @brand-danger;
    -@state-danger-bg:                #f9bdbb;
    +@state-danger-text:              #a94442;
    +@state-danger-bg:                #f2dede;
     @state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
     
     
    @@ -522,7 +522,7 @@
     //** Tooltip text color
     @tooltip-color:               #fff;
     //** Tooltip background color
    -@tooltip-bg:                  #727272;
    +@tooltip-bg:                  #000;
     @tooltip-opacity:             .9;
     
     //** Tooltip arrow width
    @@ -540,9 +540,9 @@
     //** Popover maximum width
     @popover-max-width:                   276px;
     //** Popover border color
    -@popover-border-color:                transparent;
    +@popover-border-color:                rgba(0,0,0,.2);
     //** Popover fallback border color
    -@popover-fallback-border-color:       transparent;
    +@popover-fallback-border-color:       #ccc;
     
     //** Popover title background color
     @popover-title-bg:                    darken(@popover-bg, 3%);
    @@ -555,7 +555,7 @@
     //** Popover outer arrow width
     @popover-arrow-outer-width:           (@popover-arrow-width + 1);
     //** Popover outer arrow color
    -@popover-arrow-outer-color:           fadein(@popover-border-color, 7.5%);
    +@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
     //** Popover outer arrow fallback color
     @popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
     
    @@ -598,7 +598,7 @@
     //** Background color of modal content area
     @modal-content-bg:                             #fff;
     //** Modal content border color
    -@modal-content-border-color:                   transparent;
    +@modal-content-border-color:                   rgba(0,0,0,.2);
     //** Modal content border color **for IE8**
     @modal-content-fallback-border-color:          #999;
     
    @@ -607,7 +607,7 @@
     //** Modal backdrop opacity
     @modal-backdrop-opacity:      .5;
     //** Modal header border color
    -@modal-header-border-color:   transparent;
    +@modal-header-border-color:   #e5e5e5;
     //** Modal footer border color
     @modal-footer-border-color:   @modal-header-border-color;
     
    @@ -720,21 +720,21 @@
     @panel-primary-border:        @brand-primary;
     @panel-primary-heading-bg:    @brand-primary;
     
    -@panel-success-text:          #fff;
    +@panel-success-text:          @state-success-text;
     @panel-success-border:        @state-success-border;
    -@panel-success-heading-bg:    @brand-success;
    +@panel-success-heading-bg:    @state-success-bg;
     
    -@panel-info-text:             #fff;
    +@panel-info-text:             @state-info-text;
     @panel-info-border:           @state-info-border;
    -@panel-info-heading-bg:       @brand-info;
    +@panel-info-heading-bg:       @state-info-bg;
     
    -@panel-warning-text:          #fff;
    +@panel-warning-text:          @state-warning-text;
     @panel-warning-border:        @state-warning-border;
    -@panel-warning-heading-bg:    @brand-warning;
    +@panel-warning-heading-bg:    @state-warning-bg;
     
    -@panel-danger-text:           #fff;
    +@panel-danger-text:           @state-danger-text;
     @panel-danger-border:         @state-danger-border;
    -@panel-danger-heading-bg:     @brand-danger;
    +@panel-danger-heading-bg:     @state-danger-bg;
     
     
     //== Thumbnails
    @@ -760,8 +760,8 @@
     //
     //##
     
    -@well-bg:                     #f9f9f9;
    -@well-border:                 transparent;
    +@well-bg:                     #f5f5f5;
    +@well-border:                 darken(@well-bg, 7%);
     
     
     //== Badges
    @@ -778,7 +778,7 @@
     //** Badge background color in active nav link
     @badge-active-bg:             #fff;
     
    -@badge-font-weight:           normal;
    +@badge-font-weight:           bold;
     @badge-line-height:           1;
     @badge-border-radius:         10px;
     
    @@ -820,9 +820,9 @@
     //
     //##
     
    -@close-font-weight:           normal;
    +@close-font-weight:           bold;
     @close-color:                 #000;
    -@close-text-shadow:           none;
    +@close-text-shadow:           0 1px 0 #fff;
     
     
     //== Code
    diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js
    index 7138bb01b9..1008928bc5 100644
    --- a/public/src/admin/manage/users.js
    +++ b/public/src/admin/manage/users.js
    @@ -231,41 +231,62 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable)
     		});
     
     		function handleUserCreate() {
    -			var errorEl = $('#create-modal-error');
     			$('#createUser').on('click', function() {
    -				$('#create-modal').modal('show');
    -				$('#create-modal form')[0].reset();
    -				errorEl.addClass('hide');
    +				templates.parse('admin/partials/create_user_modal', {}, function(html) {
    +					translator.translate(html, function(html) {
    +						bootbox.dialog({
    +							message: html,
    +							title: 'Create User',
    +							onEscape: true,
    +							buttons: {
    +								cancel: {
    +									label: 'Cancel',
    +									className: 'btn-link'
    +								},
    +								create: {
    +									label: 'Create',
    +									className: 'btn-primary',
    +									callback: function(e) {
    +										createUser.call(this);
    +										return false;
    +									}
    +								}
    +							}
    +						});
    +					});
    +				});
     			});
    +		}
     
    -			$('#create-modal-go').on('click', function() {
    -				var username = $('#create-user-name').val(),
    -					email = $('#create-user-email').val(),
    -					password = $('#create-user-password').val(),
    -					passwordAgain = $('#create-user-password-again').val();
    +		function createUser() {
    +			var modal = this;
    +			var username = document.getElementById('create-user-name').value;
    +			var email = document.getElementById('create-user-email').value;
    +			var password = document.getElementById('create-user-password').value;
    +			var passwordAgain = document.getElementById('create-user-password-again').value;
     
    +			var errorEl = $('#create-modal-error');
     
    -				if (password !== passwordAgain) {
    -					return errorEl.html('Error

    Passwords must match!

    ').removeClass('hide'); + if (password !== passwordAgain) { + return errorEl.html('Error

    Passwords must match!

    ').removeClass('hide'); + } + + var user = { + username: username, + email: email, + password: password + }; + + socket.emit('admin.user.createUser', user, function(err) { + if(err) { + return errorEl.translateHtml('Error

    ' + err.message + '

    ').removeClass('hide'); } - var user = { - username: username, - email: email, - password: password - }; - - socket.emit('admin.user.createUser', user, function(err) { - if(err) { - return errorEl.translateHtml('Error

    ' + err.message + '

    ').removeClass('hide'); - } - $('#create-modal').modal('hide'); - $('#create-modal').on('hidden.bs.modal', function() { - ajaxify.refresh(); - }); - app.alertSuccess('User created!'); + modal.modal('hide'); + modal.on('hidden.bs.modal', function() { + ajaxify.refresh(); }); - + app.alertSuccess('User created!'); }); } diff --git a/src/meta/css.js b/src/meta/css.js index 4834cedc95..a118d75b1a 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -39,8 +39,7 @@ module.exports = function(Meta) { paths = [ baseThemePath, path.join(__dirname, '../../node_modules'), - path.join(__dirname, '../../public/vendor/fontawesome/less'), - path.join(__dirname, '../../public/vendor/bootstrap/less') + path.join(__dirname, '../../public/vendor/fontawesome/less') ], source = '@import "font-awesome";'; diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index d6dd8c5a25..b2042d8e74 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -94,46 +94,6 @@ - - -
    diff --git a/src/views/admin/partials/create_user_modal.tpl b/src/views/admin/partials/create_user_modal.tpl new file mode 100644 index 0000000000..b065479b06 --- /dev/null +++ b/src/views/admin/partials/create_user_modal.tpl @@ -0,0 +1,21 @@ +
    +
    +
    + + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    +
    From c8ba61ac7bc6075f91ecc3a6460f24e5d227a55b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 00:05:35 +0300 Subject: [PATCH 122/386] closes #4791 --- .gitignore | 1 + src/password.js | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a8ea9cb1eb..a83a93d01f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ npm-debug.log node_modules/ sftp-config.json config.json +jsconfig.json public/src/nodebb.min.js !src/views/config.json public/css/*.css diff --git a/src/password.js b/src/password.js index 13f8c11f72..20f4c79a95 100644 --- a/src/password.js +++ b/src/password.js @@ -12,9 +12,11 @@ }; function forkChild(message, callback) { - var child = fork('./bcrypt', { - silent: true - }); + var forkProcessParams = {}; + if(global.v8debug || parseInt(process.execArgv.indexOf('--debug'), 10) !== -1) { + forkProcessParams = {execArgv: ['--debug=' + (5859), '--nolazy']}; + } + var child = fork('./bcrypt', [], forkProcessParams); child.on('message', function(msg) { if (msg.err) { From 275f5e32e8f15ca28a41ba84d47d054332d0148b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 25 Aug 2016 17:22:59 -0400 Subject: [PATCH 123/386] adding back Bootswatch paper variables override --- public/less/admin/bootstrap/variables.less | 180 ++++++++++----------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/public/less/admin/bootstrap/variables.less b/public/less/admin/bootstrap/variables.less index 03b54980ae..eed5b3cfae 100644 --- a/public/less/admin/bootstrap/variables.less +++ b/public/less/admin/bootstrap/variables.less @@ -1,4 +1,4 @@ -// +// Paper 3.3.7 // Variables // -------------------------------------------------- @@ -9,16 +9,16 @@ @gray-base: #000; @gray-darker: lighten(@gray-base, 13.5%); // #222 -@gray-dark: lighten(@gray-base, 20%); // #333 -@gray: lighten(@gray-base, 33.5%); // #555 -@gray-light: lighten(@gray-base, 46.7%); // #777 +@gray-dark: #212121; +@gray: #666; +@gray-light: #bbb; @gray-lighter: lighten(@gray-base, 93.5%); // #eee -@brand-primary: darken(#428bca, 6.5%); // #337ab7 -@brand-success: #5cb85c; -@brand-info: #5bc0de; -@brand-warning: #f0ad4e; -@brand-danger: #d9534f; +@brand-primary: #2196F3; +@brand-success: #4CAF50; +@brand-info: #9C27B0; +@brand-warning: #ff9800; +@brand-danger: #e51c23; //== Scaffolding @@ -28,7 +28,7 @@ //** Background color for ``. @body-bg: #fff; //** Global text color on ``. -@text-color: @gray-dark; +@text-color: @gray; //** Global textual link color. @link-color: @brand-primary; @@ -42,33 +42,33 @@ // //## Font, line-height, and color for body text, headings, and more. -@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-sans-serif: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; @font-family-serif: Georgia, "Times New Roman", Times, serif; //** Default monospace fonts for ``, ``, and `
    `.
     @font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
     @font-family-base:        @font-family-sans-serif;
     
    -@font-size-base:          14px;
    +@font-size-base:          13px;
     @font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
     @font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
     
    -@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
    -@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
    -@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
    -@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
    -@font-size-h5:            @font-size-base;
    -@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
    +@font-size-h1:            56px;
    +@font-size-h2:            45px;
    +@font-size-h3:            34px;
    +@font-size-h4:            24px;
    +@font-size-h5:            20px;
    +@font-size-h6:            14px;
     
     //** Unit-less `line-height` for use in components like buttons.
    -@line-height-base:        1.428571429; // 20/14
    +@line-height-base:        1.846; // 20/14
     //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
     @line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
     
     //** By default, this inherits from the ``.
     @headings-font-family:    inherit;
    -@headings-font-weight:    500;
    +@headings-font-weight:    400;
     @headings-line-height:    1.1;
    -@headings-color:          inherit;
    +@headings-color:          #444;
     
     
     //== Iconography
    @@ -88,7 +88,7 @@
     //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
     
     @padding-base-vertical:     6px;
    -@padding-base-horizontal:   12px;
    +@padding-base-horizontal:   16px;
     
     @padding-large-vertical:    10px;
     @padding-large-horizontal:  16px;
    @@ -102,8 +102,8 @@
     @line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
     @line-height-small:         1.5;
     
    -@border-radius-base:        4px;
    -@border-radius-large:       6px;
    +@border-radius-base:        3px;
    +@border-radius-large:       3px;
     @border-radius-small:       3px;
     
     //** Global color for active items (e.g., navs or dropdowns).
    @@ -144,29 +144,29 @@
     
     @btn-font-weight:                normal;
     
    -@btn-default-color:              #333;
    +@btn-default-color:              #444;
     @btn-default-bg:                 #fff;
    -@btn-default-border:             #ccc;
    +@btn-default-border:             transparent;
     
     @btn-primary-color:              #fff;
     @btn-primary-bg:                 @brand-primary;
    -@btn-primary-border:             darken(@btn-primary-bg, 5%);
    +@btn-primary-border:             transparent;
     
     @btn-success-color:              #fff;
     @btn-success-bg:                 @brand-success;
    -@btn-success-border:             darken(@btn-success-bg, 5%);
    +@btn-success-border:             transparent;
     
     @btn-info-color:                 #fff;
     @btn-info-bg:                    @brand-info;
    -@btn-info-border:                darken(@btn-info-bg, 5%);
    +@btn-info-border:                transparent;
     
     @btn-warning-color:              #fff;
     @btn-warning-bg:                 @brand-warning;
    -@btn-warning-border:             darken(@btn-warning-bg, 5%);
    +@btn-warning-border:             transparent;
     
     @btn-danger-color:               #fff;
     @btn-danger-bg:                  @brand-danger;
    -@btn-danger-border:              darken(@btn-danger-bg, 5%);
    +@btn-danger-border:              transparent;
     
     @btn-link-disabled-color:        @gray-light;
     
    @@ -181,14 +181,14 @@
     //##
     
     //** `` background color
    -@input-bg:                       #fff;
    +@input-bg:                       transparent;
     //** `` background color
    -@input-bg-disabled:              @gray-lighter;
    +@input-bg-disabled:              transparent;
     
     //** Text color for ``s
     @input-color:                    @gray;
     //** `` border color
    -@input-border:                   #ccc;
    +@input-border:                   transparent;
     
     // TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
     //** Default `.form-control` border radius
    @@ -203,7 +203,7 @@
     @input-border-focus:             #66afe9;
     
     //** Placeholder text color
    -@input-color-placeholder:        #999;
    +@input-color-placeholder:        @gray-light;
     
     //** Default `.form-control` height
     @input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
    @@ -219,7 +219,7 @@
     @legend-border-color:            #e5e5e5;
     
     //** Background color for textual input addons
    -@input-group-addon-bg:           @gray-lighter;
    +@input-group-addon-bg:           transparent;
     //** Border color for textual input addons
     @input-group-addon-border-color: @input-border;
     
    @@ -241,11 +241,11 @@
     @dropdown-divider-bg:            #e5e5e5;
     
     //** Dropdown link text color.
    -@dropdown-link-color:            @gray-dark;
    +@dropdown-link-color:            @text-color;
     //** Hover color for dropdown links.
     @dropdown-link-hover-color:      darken(@gray-dark, 5%);
     //** Hover background for dropdown links.
    -@dropdown-link-hover-bg:         #f5f5f5;
    +@dropdown-link-hover-bg:         @gray-lighter;
     
     //** Active dropdown menu item text color.
     @dropdown-link-active-color:     @component-active-color;
    @@ -259,7 +259,7 @@
     @dropdown-header-color:          @gray-light;
     
     //** Deprecated `@dropdown-caret-color` as of v3.1.0
    -@dropdown-caret-color:           #000;
    +@dropdown-caret-color:           @gray-light;
     
     
     //-- Z-index master list
    @@ -357,45 +357,45 @@
     //##
     
     // Basics of a navbar
    -@navbar-height:                    50px;
    +@navbar-height:                    64px;
     @navbar-margin-bottom:             @line-height-computed;
     @navbar-border-radius:             @border-radius-base;
     @navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
     @navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
     @navbar-collapse-max-height:       340px;
     
    -@navbar-default-color:             #777;
    -@navbar-default-bg:                #f8f8f8;
    -@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
    +@navbar-default-color:             @gray-light;
    +@navbar-default-bg:                #fff;
    +@navbar-default-border:            transparent;
     
     // Navbar links
    -@navbar-default-link-color:                #777;
    -@navbar-default-link-hover-color:          #333;
    +@navbar-default-link-color:                @gray;
    +@navbar-default-link-hover-color:          @gray-dark;
     @navbar-default-link-hover-bg:             transparent;
    -@navbar-default-link-active-color:         #555;
    +@navbar-default-link-active-color:         @gray-dark;
     @navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
     @navbar-default-link-disabled-color:       #ccc;
     @navbar-default-link-disabled-bg:          transparent;
     
     // Navbar brand label
     @navbar-default-brand-color:               @navbar-default-link-color;
    -@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
    +@navbar-default-brand-hover-color:         @navbar-default-link-hover-color;
     @navbar-default-brand-hover-bg:            transparent;
     
     // Navbar toggle
    -@navbar-default-toggle-hover-bg:           #ddd;
    -@navbar-default-toggle-icon-bar-bg:        #888;
    -@navbar-default-toggle-border-color:       #ddd;
    +@navbar-default-toggle-hover-bg:           transparent;
    +@navbar-default-toggle-icon-bar-bg:        rgba(0,0,0,0.5);
    +@navbar-default-toggle-border-color:       transparent;
     
     
     //=== Inverted navbar
     // Reset inverted navbar basics
    -@navbar-inverse-color:                      lighten(@gray-light, 15%);
    -@navbar-inverse-bg:                         #222;
    -@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
    +@navbar-inverse-color:                      @gray-light;
    +@navbar-inverse-bg:                         @brand-primary;
    +@navbar-inverse-border:                     transparent;
     
     // Inverted navbar links
    -@navbar-inverse-link-color:                 lighten(@gray-light, 15%);
    +@navbar-inverse-link-color:                 lighten(@brand-primary, 30%);
     @navbar-inverse-link-hover-color:           #fff;
     @navbar-inverse-link-hover-bg:              transparent;
     @navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
    @@ -408,10 +408,10 @@
     @navbar-inverse-brand-hover-color:          #fff;
     @navbar-inverse-brand-hover-bg:             transparent;
     
    -// Inverted navbar toggle
    -@navbar-inverse-toggle-hover-bg:            #333;
    -@navbar-inverse-toggle-icon-bar-bg:         #fff;
    -@navbar-inverse-toggle-border-color:        #333;
    +// Inverted navbar toggle\
    +@navbar-inverse-toggle-hover-bg:            transparent;
    +@navbar-inverse-toggle-icon-bar-bg:         rgba(0,0,0,0.5);
    +@navbar-inverse-toggle-border-color:        transparent;
     
     
     //== Navs
    @@ -426,15 +426,15 @@
     @nav-disabled-link-hover-color:             @gray-light;
     
     //== Tabs
    -@nav-tabs-border-color:                     #ddd;
    +@nav-tabs-border-color:                     transparent;
     
     @nav-tabs-link-hover-border-color:          @gray-lighter;
     
    -@nav-tabs-active-link-hover-bg:             @body-bg;
    +@nav-tabs-active-link-hover-bg:             transparent;
     @nav-tabs-active-link-hover-color:          @gray;
    -@nav-tabs-active-link-hover-border-color:   #ddd;
    +@nav-tabs-active-link-hover-border-color:   transparent;
     
    -@nav-tabs-justified-link-border-color:            #ddd;
    +@nav-tabs-justified-link-border-color:            @nav-tabs-border-color;
     @nav-tabs-justified-active-link-border-color:     @body-bg;
     
     //== Pills
    @@ -486,8 +486,8 @@
     
     @jumbotron-padding:              30px;
     @jumbotron-color:                inherit;
    -@jumbotron-bg:                   @gray-lighter;
    -@jumbotron-heading-color:        inherit;
    +@jumbotron-bg:                   #f5f5f5;
    +@jumbotron-heading-color:        @headings-color;
     @jumbotron-font-size:            ceil((@font-size-base * 1.5));
     @jumbotron-heading-font-size:    ceil((@font-size-base * 4.5));
     
    @@ -496,20 +496,20 @@
     //
     //## Define colors for form feedback states and, by default, alerts.
     
    -@state-success-text:             #3c763d;
    +@state-success-text:             @brand-success;
     @state-success-bg:               #dff0d8;
     @state-success-border:           darken(spin(@state-success-bg, -10), 5%);
     
    -@state-info-text:                #31708f;
    -@state-info-bg:                  #d9edf7;
    +@state-info-text:                @brand-info;
    +@state-info-bg:                  #e1bee7;
     @state-info-border:              darken(spin(@state-info-bg, -10), 7%);
     
    -@state-warning-text:             #8a6d3b;
    -@state-warning-bg:               #fcf8e3;
    +@state-warning-text:             @brand-warning;
    +@state-warning-bg:               #ffe0b2;
     @state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
     
    -@state-danger-text:              #a94442;
    -@state-danger-bg:                #f2dede;
    +@state-danger-text:              @brand-danger;
    +@state-danger-bg:                #f9bdbb;
     @state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
     
     
    @@ -522,7 +522,7 @@
     //** Tooltip text color
     @tooltip-color:               #fff;
     //** Tooltip background color
    -@tooltip-bg:                  #000;
    +@tooltip-bg:                  #727272;
     @tooltip-opacity:             .9;
     
     //** Tooltip arrow width
    @@ -540,9 +540,9 @@
     //** Popover maximum width
     @popover-max-width:                   276px;
     //** Popover border color
    -@popover-border-color:                rgba(0,0,0,.2);
    +@popover-border-color:                transparent;
     //** Popover fallback border color
    -@popover-fallback-border-color:       #ccc;
    +@popover-fallback-border-color:       transparent;
     
     //** Popover title background color
     @popover-title-bg:                    darken(@popover-bg, 3%);
    @@ -555,7 +555,7 @@
     //** Popover outer arrow width
     @popover-arrow-outer-width:           (@popover-arrow-width + 1);
     //** Popover outer arrow color
    -@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
    +@popover-arrow-outer-color:           fadein(@popover-border-color, 12%);
     //** Popover outer arrow fallback color
     @popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
     
    @@ -598,7 +598,7 @@
     //** Background color of modal content area
     @modal-content-bg:                             #fff;
     //** Modal content border color
    -@modal-content-border-color:                   rgba(0,0,0,.2);
    +@modal-content-border-color:                   transparent;
     //** Modal content border color **for IE8**
     @modal-content-fallback-border-color:          #999;
     
    @@ -607,7 +607,7 @@
     //** Modal backdrop opacity
     @modal-backdrop-opacity:      .5;
     //** Modal header border color
    -@modal-header-border-color:   #e5e5e5;
    +@modal-header-border-color:   transparent;
     //** Modal footer border color
     @modal-footer-border-color:   @modal-header-border-color;
     
    @@ -720,21 +720,21 @@
     @panel-primary-border:        @brand-primary;
     @panel-primary-heading-bg:    @brand-primary;
     
    -@panel-success-text:          @state-success-text;
    +@panel-success-text:          #fff;
     @panel-success-border:        @state-success-border;
    -@panel-success-heading-bg:    @state-success-bg;
    +@panel-success-heading-bg:    @brand-success;
     
    -@panel-info-text:             @state-info-text;
    +@panel-info-text:             #fff;
     @panel-info-border:           @state-info-border;
    -@panel-info-heading-bg:       @state-info-bg;
    +@panel-info-heading-bg:       @brand-info;
     
    -@panel-warning-text:          @state-warning-text;
    +@panel-warning-text:          #fff;
     @panel-warning-border:        @state-warning-border;
    -@panel-warning-heading-bg:    @state-warning-bg;
    +@panel-warning-heading-bg:    @brand-warning;
     
    -@panel-danger-text:           @state-danger-text;
    +@panel-danger-text:           #fff;
     @panel-danger-border:         @state-danger-border;
    -@panel-danger-heading-bg:     @state-danger-bg;
    +@panel-danger-heading-bg:     @brand-danger;
     
     
     //== Thumbnails
    @@ -761,7 +761,7 @@
     //##
     
     @well-bg:                     #f5f5f5;
    -@well-border:                 darken(@well-bg, 7%);
    +@well-border:                 transparent;
     
     
     //== Badges
    @@ -778,7 +778,7 @@
     //** Badge background color in active nav link
     @badge-active-bg:             #fff;
     
    -@badge-font-weight:           bold;
    +@badge-font-weight:           normal;
     @badge-line-height:           1;
     @badge-border-radius:         10px;
     
    @@ -820,9 +820,9 @@
     //
     //##
     
    -@close-font-weight:           bold;
    +@close-font-weight:           normal;
     @close-color:                 #000;
    -@close-text-shadow:           0 1px 0 #fff;
    +@close-text-shadow:           none;
     
     
     //== Code
    @@ -866,4 +866,4 @@
     //** Point at which .dl-horizontal becomes horizontal
     @dl-horizontal-breakpoint:    @grid-float-breakpoint;
     //** Horizontal line color.
    -@hr-border:                   @gray-lighter;
    +@hr-border:                   @gray-lighter;
    \ No newline at end of file
    
    From 1339c4155df0bbbc67065377f487cbf6c65e272d Mon Sep 17 00:00:00 2001
    From: barisusakli 
    Date: Fri, 26 Aug 2016 11:19:12 +0300
    Subject: [PATCH 124/386] up composer
    
    ---
     package.json | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/package.json b/package.json
    index b498b13924..97c0d9c6bb 100644
    --- a/package.json
    +++ b/package.json
    @@ -47,7 +47,7 @@
         "morgan": "^1.3.2",
         "mousetrap": "^1.5.3",
         "nconf": "~0.8.2",
    -    "nodebb-plugin-composer-default": "4.1.9",
    +    "nodebb-plugin-composer-default": "4.1.10",
         "nodebb-plugin-dbsearch": "1.0.2",
         "nodebb-plugin-emoji-extended": "1.1.1",
         "nodebb-plugin-emoji-one": "1.1.5",
    
    From 97f0d2438f31ebbd2bbaa837ed30066586b95341 Mon Sep 17 00:00:00 2001
    From: barisusakli 
    Date: Fri, 26 Aug 2016 12:33:44 +0300
    Subject: [PATCH 125/386] up timeout to 500ms
    
    ---
     src/controllers/admin/info.js | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js
    index 2459ad7140..00a5694937 100644
    --- a/src/controllers/admin/info.js
    +++ b/src/controllers/admin/info.js
    @@ -25,7 +25,7 @@ infoController.get = function(req, res, next) {
     			return (a.os.hostname < b.os.hostname) ? -1 : (a.os.hostname > b.os.hostname) ? 1 : 0;
     		});
     		res.render('admin/development/info', {info: data, infoJSON: JSON.stringify(data, null, 4), host: os.hostname(), port: nconf.get('port')});
    -	}, 300);
    +	}, 500);
     };
     
     pubsub.on('sync:node:info:start', function() {
    
    From e5ecb333f9304bdac21a26319d0d0415ad400db6 Mon Sep 17 00:00:00 2001
    From: barisusakli 
    Date: Fri, 26 Aug 2016 12:53:00 +0300
    Subject: [PATCH 126/386] closes #4978
    
    ---
     public/src/require-config.js            | 11 +++++++++++
     src/meta/js.js                          |  1 +
     src/views/admin/header.tpl              | 17 ++---------------
     src/views/partials/requirejs-config.tpl | 13 -------------
     4 files changed, 14 insertions(+), 28 deletions(-)
     create mode 100644 public/src/require-config.js
     delete mode 100644 src/views/partials/requirejs-config.tpl
    
    diff --git a/public/src/require-config.js b/public/src/require-config.js
    new file mode 100644
    index 0000000000..0ad2f8a58c
    --- /dev/null
    +++ b/public/src/require-config.js
    @@ -0,0 +1,11 @@
    +require.config({
    +	baseUrl: config.relative_path + "/src/modules",
    +	waitSeconds: 7,
    +	urlArgs: "v=" + config['cache-buster'],
    +	paths: {
    +		'forum': '../client',
    +		'admin': '../admin',
    +		'vendor': '../../vendor',
    +		'plugins': '../../plugins'
    +	}
    +});
    diff --git a/src/meta/js.js b/src/meta/js.js
    index b781a3b06f..ffd66f010e 100644
    --- a/src/meta/js.js
    +++ b/src/meta/js.js
    @@ -26,6 +26,7 @@ module.exports = function(Meta) {
     				'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js',
     				'public/vendor/jquery/textcomplete/jquery.textcomplete.js',
     				'public/vendor/requirejs/require.js',
    +				'public/src/require-config.js',
     				'public/vendor/bootbox/bootbox.min.js',
     				'public/vendor/tinycon/tinycon.js',
     				'public/vendor/xregexp/xregexp.js',
    diff --git a/src/views/admin/header.tpl b/src/views/admin/header.tpl
    index 3d797ed5be..b5a0f196c3 100644
    --- a/src/views/admin/header.tpl
    +++ b/src/views/admin/header.tpl
    @@ -16,7 +16,8 @@
     			template: "{template.name}",
     			user: JSON.parse('{{userJSON}}'),
     			config: JSON.parse(decodeURIComponent("{{adminConfigJSON}}")),
    -			flags: {}
    +			flags: {},
    +			inAdmin: true
     		};
     	
     
    @@ -30,20 +31,6 @@
     		
     		
     		
    -		
     		
     		
     		
    diff --git a/src/views/partials/requirejs-config.tpl b/src/views/partials/requirejs-config.tpl
    deleted file mode 100644
    index c11b4aa9a3..0000000000
    --- a/src/views/partials/requirejs-config.tpl
    +++ /dev/null
    @@ -1,13 +0,0 @@
    -
    \ No newline at end of file
    
    From de34a98636548cf51f76802240af5cce7062f284 Mon Sep 17 00:00:00 2001
    From: barisusakli 
    Date: Fri, 26 Aug 2016 13:08:29 +0300
    Subject: [PATCH 127/386] up themes
    
    ---
     package.json | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/package.json b/package.json
    index 97c0d9c6bb..97c7ca45fa 100644
    --- a/package.json
    +++ b/package.json
    @@ -57,8 +57,8 @@
         "nodebb-plugin-spam-be-gone": "0.4.10",
         "nodebb-rewards-essentials": "0.0.9",
         "nodebb-theme-lavender": "3.0.13",
    -    "nodebb-theme-persona": "4.1.33",
    -    "nodebb-theme-vanilla": "5.1.18",
    +    "nodebb-theme-persona": "4.1.34",
    +    "nodebb-theme-vanilla": "5.1.19",
         "nodebb-widget-essentials": "2.0.10",
         "nodemailer": "2.0.0",
         "nodemailer-sendmail-transport": "1.0.0",
    
    From b7fbc7a5319c2e5ab4670b53465d8cd3a93a9b19 Mon Sep 17 00:00:00 2001
    From: NodeBB Misty 
    Date: Fri, 26 Aug 2016 09:03:14 -0400
    Subject: [PATCH 128/386] Latest translations and fallbacks
    
    ---
     public/language/ar/global.json        | 4 +++-
     public/language/bg/global.json        | 2 ++
     public/language/bn/global.json        | 4 +++-
     public/language/cs/global.json        | 4 +++-
     public/language/da/global.json        | 4 +++-
     public/language/de/global.json        | 4 +++-
     public/language/el/global.json        | 4 +++-
     public/language/en@pirate/global.json | 4 +++-
     public/language/en_US/global.json     | 4 +++-
     public/language/es/global.json        | 4 +++-
     public/language/et/global.json        | 4 +++-
     public/language/fa_IR/global.json     | 4 +++-
     public/language/fi/global.json        | 4 +++-
     public/language/fr/global.json        | 4 +++-
     public/language/gl/global.json        | 4 +++-
     public/language/he/global.json        | 4 +++-
     public/language/hu/global.json        | 4 +++-
     public/language/id/global.json        | 4 +++-
     public/language/it/global.json        | 4 +++-
     public/language/ja/global.json        | 4 +++-
     public/language/ko/global.json        | 4 +++-
     public/language/lt/global.json        | 4 +++-
     public/language/ms/global.json        | 4 +++-
     public/language/nb/global.json        | 4 +++-
     public/language/nl/global.json        | 4 +++-
     public/language/pl/global.json        | 4 +++-
     public/language/pt_BR/global.json     | 4 +++-
     public/language/ro/global.json        | 4 +++-
     public/language/ru/global.json        | 4 +++-
     public/language/rw/global.json        | 4 +++-
     public/language/sc/global.json        | 4 +++-
     public/language/sk/global.json        | 4 +++-
     public/language/sl/global.json        | 4 +++-
     public/language/sr/global.json        | 4 +++-
     public/language/sv/global.json        | 4 +++-
     public/language/th/global.json        | 4 +++-
     public/language/tr/global.json        | 4 +++-
     public/language/vi/global.json        | 4 +++-
     public/language/zh_CN/error.json      | 8 ++++----
     public/language/zh_CN/global.json     | 6 ++++--
     public/language/zh_CN/login.json      | 2 +-
     public/language/zh_CN/modules.json    | 2 +-
     public/language/zh_CN/topic.json      | 6 +++---
     public/language/zh_TW/global.json     | 4 +++-
     44 files changed, 129 insertions(+), 49 deletions(-)
    
    diff --git a/public/language/ar/global.json b/public/language/ar/global.json
    index 96356a3bce..2e4d39d113 100644
    --- a/public/language/ar/global.json
    +++ b/public/language/ar/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "لم يتم العثور",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "خطأ داخلي.",
    +    "500.title": "Internal Error.",
         "500.message": "عفوا! يبدو وكأنه شيء ذهب على نحو خاطئ!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "تسجيل",
         "login": "دخول",
         "please_log_in": "المرجو تسجيل الدخول",
    diff --git a/public/language/bg/global.json b/public/language/bg/global.json
    index 189c8d3990..6a68bfcec3 100644
    --- a/public/language/bg/global.json
    +++ b/public/language/bg/global.json
    @@ -9,6 +9,8 @@
         "404.message": "Изглежда сте се опитали да посетите страница, която не съществува. Върнете се към началната страница.",
         "500.title": "Вътрешна грешка.",
         "500.message": "Опа! Изглежда нещо се обърка!",
    +    "400.title": "Грешна заявка.",
    +    "400.message": "Тази връзка изглежда повредена. Моля, проверете я и опитайте отново. В противен случай се върнете на началната страница.",
         "register": "Регистрация",
         "login": "Вход",
         "please_log_in": "Моля, влезте",
    diff --git a/public/language/bn/global.json b/public/language/bn/global.json
    index bfde1d9003..f678961fcd 100644
    --- a/public/language/bn/global.json
    +++ b/public/language/bn/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "পাওয়া যায়নি",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "অভ্যন্তরীণ ত্রুটি।",
    +    "500.title": "Internal Error.",
         "500.message": "ওহো! কিছু ভুল হয়েছে মনে হচ্ছে!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "নিবন্ধন",
         "login": "প্রবেশ",
         "please_log_in": "অনুগ্রহ করে প্রবেশ করুন",
    diff --git a/public/language/cs/global.json b/public/language/cs/global.json
    index d78d3797f4..80a7ee4825 100644
    --- a/public/language/cs/global.json
    +++ b/public/language/cs/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Možná byste měli se zkusit přihlásit?",
         "404.title": "Stránka nenalezena",
         "404.message": "Zdá se, že jste narazil/a na stránku která neexistuje. Vrátit se zpět na domovskou stránku.",
    -    "500.title": "Neznámá chyba",
    +    "500.title": "Internal Error.",
         "500.message": "Jejda, vypadá to, že se něco pokazilo.",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registrovat",
         "login": "Přihlásit se",
         "please_log_in": "Přihlašte se, prosím",
    diff --git a/public/language/da/global.json b/public/language/da/global.json
    index 2eda261502..5f6d693e1f 100644
    --- a/public/language/da/global.json
    +++ b/public/language/da/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Måske du skulle prøve og logge ind?",
         "404.title": "Ikke fundet",
         "404.message": "Det ser ud til du er stødt på en side der ikke finder. Retuner til  forsiden.",
    -    "500.title": "Intern fejl.",
    +    "500.title": "Internal Error.",
         "500.message": "Ups! Ser ud til at noget gik galt!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Tilmeld",
         "login": "Log ind",
         "please_log_in": "Venligst log ind",
    diff --git a/public/language/de/global.json b/public/language/de/global.json
    index 496c212943..775e81cbfa 100644
    --- a/public/language/de/global.json
    +++ b/public/language/de/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Du solltest Dich anmelden.",
         "404.title": " Nicht Gefunden",
         "404.message": "Diese Seite existiert nicht. Zur Homepage zurückkehren.",
    -    "500.title": "Interner Fehler.",
    +    "500.title": "Internal Error.",
         "500.message": "Ups! Scheint als wäre etwas schief gelaufen!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registrieren",
         "login": "Anmelden",
         "please_log_in": "Bitte anmelden",
    diff --git a/public/language/el/global.json b/public/language/el/global.json
    index 32eacb6466..1c475bdf03 100644
    --- a/public/language/el/global.json
    +++ b/public/language/el/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "Δεν βρέθηκε",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "Εσωτερικό σφάλμα.",
    +    "500.title": "Internal Error.",
         "500.message": "Ουπς! Φαίνεται πως κάτι πήγε στραβά!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Εγγραφή",
         "login": "Σύνδεση",
         "please_log_in": "Παρακαλώ Συνδέσου",
    diff --git a/public/language/en@pirate/global.json b/public/language/en@pirate/global.json
    index 6c86c89999..9bb8beecd3 100644
    --- a/public/language/en@pirate/global.json
    +++ b/public/language/en@pirate/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "T'ere be nut'in 'ere",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "Broken beam.",
    +    "500.title": "Internal Error.",
         "500.message": "Looks like we've got somethin' in th' sails.",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Register",
         "login": "Login",
         "please_log_in": "Please Log In",
    diff --git a/public/language/en_US/global.json b/public/language/en_US/global.json
    index e7f511d90a..004929c8a8 100644
    --- a/public/language/en_US/global.json
    +++ b/public/language/en_US/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "Not Found",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "Internal error.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Looks like something went wrong!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Register",
         "login": "Login",
         "please_log_in": "Please Log In",
    diff --git a/public/language/es/global.json b/public/language/es/global.json
    index 3cf4afbbae..d75547742a 100644
    --- a/public/language/es/global.json
    +++ b/public/language/es/global.json
    @@ -7,8 +7,10 @@
         "403.login": "¿Quizás deberías  intentar acceder?",
         "404.title": "No encontrado",
         "404.message": "Al parecer has llegado a una página a la cual no tienes permisos para acceder. Volver a la  página de inicio .",
    -    "500.title": "Error Interno.",
    +    "500.title": "Error interno.",
         "500.message": "¡Ooops! ¡Parece que algo salió mal! No te preocupes, ¡nuestros simios hiperinteligentes lo solucionarán!",
    +    "400.title": "Petición incorrecta.",
    +    "400.message": "Parece que la dirección es errónea, por favor compruébala y prueba otra vez. En caso contrario vuelve al inicio.",
         "register": "Registrarse",
         "login": "Conectarse",
         "please_log_in": "Por favor, identifíquese.",
    diff --git a/public/language/et/global.json b/public/language/et/global.json
    index 3add6a941c..ba6bb6c304 100644
    --- a/public/language/et/global.json
    +++ b/public/language/et/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Äkki peaksid sisse logima?",
         "404.title": "Ei leitud",
         "404.message": "Tundub, et lehte mida otsid, ei eksisteeri. Mine tagasi avalehele.",
    -    "500.title": "Süsteemi viga",
    +    "500.title": "Internal Error.",
         "500.message": "Oih! Midagi läks valesti!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registreeri",
         "login": "Logi sisse",
         "please_log_in": "Palun logi sisse",
    diff --git a/public/language/fa_IR/global.json b/public/language/fa_IR/global.json
    index ce48647e40..a8da7a5cd6 100644
    --- a/public/language/fa_IR/global.json
    +++ b/public/language/fa_IR/global.json
    @@ -7,8 +7,10 @@
         "403.login": "شاید باید وارد شوید؟",
         "404.title": "یافت نشد",
         "404.message": "به نظر میاید شما به صفحه ای برخورد کرده اید که وجود ندارد. بازگشت به صفحه ی خانه",
    -    "500.title": "خطای درونی.",
    +    "500.title": "Internal Error.",
         "500.message": "اوه! گویا اشتباهی رخ داده!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "نام‌نویسی",
         "login": "درون آمدن",
         "please_log_in": "لطفا به درون بیایید",
    diff --git a/public/language/fi/global.json b/public/language/fi/global.json
    index 8488b07bfe..a2fd93afde 100644
    --- a/public/language/fi/global.json
    +++ b/public/language/fi/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Sinun pitäisi kai kirjautua sisään?",
         "404.title": "Ei löydy",
         "404.message": "Olet päätynyt sivulle, jota ei ole olemassa. Palaa etusivulle.",
    -    "500.title": "Sisäinen virhe.",
    +    "500.title": "Internal Error.",
         "500.message": "Oho! Jotain meni pieleen!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Rekisteröidy",
         "login": "Kirjaudu",
         "please_log_in": "Kirjaudu, ole hyvä",
    diff --git a/public/language/fr/global.json b/public/language/fr/global.json
    index 10b70a2148..ba4388b9fe 100644
    --- a/public/language/fr/global.json
    +++ b/public/language/fr/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Peut-être deviez vous  essayer de vous connecter?",
         "404.title": "Introuvable",
         "404.message": "Il semble que vous ayez atteint une page qui n'existe pas. Retourner à la page d'accueil.",
    -    "500.title": "Erreur interne.",
    +    "500.title": "Erreur Interne.",
         "500.message": "Oops ! Il semblerait que quelque chose se soit mal passé !",
    +    "400.title": "Requête erronée.",
    +    "400.message": "Il semble que ce lien ne soit pas correct, merci de le vérifier. Sinon, retournez à la page d'accueil.",
         "register": "S'inscrire",
         "login": "Se connecter",
         "please_log_in": "Veuillez vous connecter",
    diff --git a/public/language/gl/global.json b/public/language/gl/global.json
    index 8d1301e078..c00aec98d1 100644
    --- a/public/language/gl/global.json
    +++ b/public/language/gl/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Quizais deberías tentar iniciar sesión?",
         "404.title": "Non Atopado",
         "404.message": "Ao parecer, esta páxina non existe. Volver ao Inicio.",
    -    "500.title": "Erro interno.",
    +    "500.title": "Internal Error.",
         "500.message": "Ups! Parece que algo saíu mal!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Rexistrarse",
         "login": "Conectarse",
         "please_log_in": "Por favor, conéctate",
    diff --git a/public/language/he/global.json b/public/language/he/global.json
    index 6e76c19ea3..ea6d32468d 100644
    --- a/public/language/he/global.json
    +++ b/public/language/he/global.json
    @@ -7,8 +7,10 @@
         "403.login": "נסה להתחבר.",
         "404.title": "לא נמצא",
         "404.message": "נראה שהגעת לעמוד שלא קיים. חזור לעמוד הבית",
    -    "500.title": "שגיאה פנימית.",
    +    "500.title": "Internal Error.",
         "500.message": "אופס! נראה שמשהו השתבש!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "הרשמה",
         "login": "התחברות",
         "please_log_in": "אנא התחבר",
    diff --git a/public/language/hu/global.json b/public/language/hu/global.json
    index b7f0edef28..e7111389bc 100644
    --- a/public/language/hu/global.json
    +++ b/public/language/hu/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Talán meg kellene próbálnod belépni?",
         "404.title": "Nincs találat",
         "404.message": "Úgy tűnik, hogy rábukkantál egy olyan oldalra ami nem létezik. Visszatérés a kezdőlapra",
    -    "500.title": "Belső hiba.",
    +    "500.title": "Internal Error.",
         "500.message": "Hoppá! Úgy tűnik valami hiba történt!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Regisztráció",
         "login": "Belépés",
         "please_log_in": "Jelentkezzünk be",
    diff --git a/public/language/id/global.json b/public/language/id/global.json
    index aac14e74d7..6a005844c4 100644
    --- a/public/language/id/global.json
    +++ b/public/language/id/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Mungkin kamu harus mencoba untuk login?",
         "404.title": "Tidak ditemukan",
         "404.message": "Kamu kelihatan mengakses halaman yang tidak ada. Kembali ke beranda.",
    -    "500.title": "Kesalahan internal",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Terjadi kesalahan",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Daftar",
         "login": "Login",
         "please_log_in": "Silakan Log In",
    diff --git a/public/language/it/global.json b/public/language/it/global.json
    index 0bebb5cd9e..a9b0e569b7 100644
    --- a/public/language/it/global.json
    +++ b/public/language/it/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Forse dovresti effettuare l'accesso?",
         "404.title": "Non trovato",
         "404.message": "Sembra tu sia arrivato ad una pagina che non esiste. Torna alla home page.",
    -    "500.title": "Errore interno.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Qualcosa non funziona come si deve!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registrazione",
         "login": "Login",
         "please_log_in": "Per favore Accedi",
    diff --git a/public/language/ja/global.json b/public/language/ja/global.json
    index 1e89ac1a32..038f6333f0 100644
    --- a/public/language/ja/global.json
    +++ b/public/language/ja/global.json
    @@ -7,8 +7,10 @@
         "403.login": "権限を持っている場合はログインすると閲覧出来ます。",
         "404.title": "見つかりません",
         "404.message": "あなたは存在してないページを訪問してます。ホームページに戻ります。",
    -    "500.title": "内部エラー",
    +    "500.title": "Internal Error.",
         "500.message": "何か問題が発生しているようです。",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "登録",
         "login": "ログイン",
         "please_log_in": "ログインください",
    diff --git a/public/language/ko/global.json b/public/language/ko/global.json
    index e404ac910a..94905f6ffc 100644
    --- a/public/language/ko/global.json
    +++ b/public/language/ko/global.json
    @@ -7,8 +7,10 @@
         "403.login": "로그인되어 있는지 확인해 주세요.",
         "404.title": "페이지를 찾을 수 없습니다.",
         "404.message": "존재하지 않는 페이지에 접근하였습니다. 홈 페이지로 이동합니다.",
    -    "500.title": "내부 오류가 발생했습니다.",
    +    "500.title": "Internal Error.",
         "500.message": "알 수 없는 오류가 발생했습니다.",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "회원가입",
         "login": "로그인",
         "please_log_in": "로그인해 주세요.",
    diff --git a/public/language/lt/global.json b/public/language/lt/global.json
    index 3892b4ede8..6f5399905d 100644
    --- a/public/language/lt/global.json
    +++ b/public/language/lt/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Tikriausiai tu turėtum pabandyt prisijungt?",
         "404.title": "Nerasta",
         "404.message": "Pasirodo sėdi puslapyje kurio net nėra. Grįžk į namų puslapį.",
    -    "500.title": "Vidinė klaida.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Atrodo, kad kažkas nutiko!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registruotis",
         "login": "Prisijungti",
         "please_log_in": "Prašome prisijungti",
    diff --git a/public/language/ms/global.json b/public/language/ms/global.json
    index e226d5f93c..dc65efdc18 100644
    --- a/public/language/ms/global.json
    +++ b/public/language/ms/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Mungkin anda boleh cuba log masuk?",
         "404.title": "tidak dijumpai",
         "404.message": "Halaman yang diminta tidak wujud. Kembali ke halaman utama.",
    -    "500.title": "ralat dalaman",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Macam ada yang tidak kena",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Daftar",
         "login": "Log Masuk",
         "please_log_in": "Sila log masuk",
    diff --git a/public/language/nb/global.json b/public/language/nb/global.json
    index 1e8af5dc12..10d96d584e 100644
    --- a/public/language/nb/global.json
    +++ b/public/language/nb/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Kanskje du skal prøve å logge inn?",
         "404.title": "Ikke funnet",
         "404.message": "Du har funnet en side som ikke eksisterer. Returner til startsiden?",
    -    "500.title": "Intern feil.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Ser ut som noe gikk galt!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registrer",
         "login": "Logg inn",
         "please_log_in": "Vennligst logg inn",
    diff --git a/public/language/nl/global.json b/public/language/nl/global.json
    index c2cf89003a..59c43d4893 100644
    --- a/public/language/nl/global.json
    +++ b/public/language/nl/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Je kan proberen in te loggen?",
         "404.title": "Niet gevonden",
         "404.message": "Deze pagina bestaat niet. Klik hier om naar de hoofdpagina van deze website te navigeren.",
    -    "500.title": "Interne fout.",
    +    "500.title": "Internal Error.",
         "500.message": "Oeps! Ziet er naar uit dat iets fout ging!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registeren",
         "login": "Login",
         "please_log_in": "Aanmelden",
    diff --git a/public/language/pl/global.json b/public/language/pl/global.json
    index f3a25ee6b2..4054a256fe 100644
    --- a/public/language/pl/global.json
    +++ b/public/language/pl/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Może powinieneś się zalogować?",
         "404.title": "Nie znaleziono",
         "404.message": "Wygląda na to, że trafiłeś na stronę, która nie istnieje. Wróć do strony głównej.",
    -    "500.title": "Błąd wewnętrzny",
    +    "500.title": "Internal Error.",
         "500.message": "Ups! Coś poszło nie tak.",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Zarejestruj się",
         "login": "Zaloguj się",
         "please_log_in": "Proszę się zalogować",
    diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json
    index 69c907a516..27e5782a3d 100644
    --- a/public/language/pt_BR/global.json
    +++ b/public/language/pt_BR/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Talvez você deveria tentar fazer login?",
         "404.title": "Não Encontrado",
         "404.message": "Parece que você chegou à uma página que não existe. Voltar para a página inicial.",
    -    "500.title": "Erro interno.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Parece que algo deu errado!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Cadastrar",
         "login": "Login",
         "please_log_in": "Por Favor Efetue o Login",
    diff --git a/public/language/ro/global.json b/public/language/ro/global.json
    index 7d2f15e71c..7a843384a0 100644
    --- a/public/language/ro/global.json
    +++ b/public/language/ro/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Poate ar trebui să te autentifici?",
         "404.title": "Nu a fost găsit",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "Eroare internă.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Se pare că ceva a mers greșit!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Înregistrare",
         "login": "Autentificare",
         "please_log_in": "Autentifică-te",
    diff --git a/public/language/ru/global.json b/public/language/ru/global.json
    index d5ed6dc2b9..2e453f412e 100644
    --- a/public/language/ru/global.json
    +++ b/public/language/ru/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Возможно Вам следует войти под своим аккаунтом?",
         "404.title": "Страница не найдена",
         "404.message": "Вы пытаетесь перейти на страницу, которой не существует. Вам стоит вернутся на главную страницу.",
    -    "500.title": "Внутренняя ошибка.",
    +    "500.title": "Internal Error.",
         "500.message": "Упс! Похоже, что-то пошло не так!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Зарегистрироваться",
         "login": "Войти",
         "please_log_in": "Пожалуйста, войдите под своим аккаунтом",
    diff --git a/public/language/rw/global.json b/public/language/rw/global.json
    index 1f82d7cdfd..088182851c 100644
    --- a/public/language/rw/global.json
    +++ b/public/language/rw/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Wenda ahari ukeneye kugerageza kwinjiramo",
         "404.title": "Ntacyabonetse",
         "404.message": "Biragaragara ko wageze kuri paji itariho ikintu. Subira Imbere.",
    -    "500.title": "Hari ikibazo cya tekinike imbere. ",
    +    "500.title": "Internal Error.",
         "500.message": "Ye baba we! Ntibikunze!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Iyandikishe",
         "login": "Injiramo",
         "please_log_in": "Injiramo",
    diff --git a/public/language/sc/global.json b/public/language/sc/global.json
    index 9f7f952155..7c70438c6c 100644
    --- a/public/language/sc/global.json
    +++ b/public/language/sc/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "No Agatadu",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "Faddina interna.",
    +    "500.title": "Internal Error.",
         "500.message": "Oops! Paret chi carchi cosa est andada male!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registra·ti",
         "login": "Intra",
         "please_log_in": "Pro praghere Intra",
    diff --git a/public/language/sk/global.json b/public/language/sk/global.json
    index 14cc91fb07..f292d3f667 100644
    --- a/public/language/sk/global.json
    +++ b/public/language/sk/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "Stránka nenájdená",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "Neznámá chyba",
    +    "500.title": "Internal Error.",
         "500.message": "Jejda, vyzerá, že sa niečo pokazilo.",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registrovať",
         "login": "Prihlásiť sa",
         "please_log_in": "Prosím, prihláste sa",
    diff --git a/public/language/sl/global.json b/public/language/sl/global.json
    index 911fb6a7ff..993d32a131 100644
    --- a/public/language/sl/global.json
    +++ b/public/language/sl/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Morda bi se raje  prijavili?",
         "404.title": "Ni mogoče najti",
         "404.message": "Kot kaže ste naleteli na stran, ki ne obstaja. Vrnite se na začetno stran.",
    -    "500.title": "Notranja napaka.",
    +    "500.title": "Internal Error.",
         "500.message": "Ups! Nekaj je šlo narobe!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registracija",
         "login": "Prijava",
         "please_log_in": "Prosimo prijavite se",
    diff --git a/public/language/sr/global.json b/public/language/sr/global.json
    index 97e71581ea..6cee270049 100644
    --- a/public/language/sr/global.json
    +++ b/public/language/sr/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Можда би требало да се пријавите?",
         "404.title": "Не постоји",
         "404.message": "Изгледа да сте наишли на страницу која не постоји. Вратите се на почетну страницу..",
    -    "500.title": "Унутрашња грешка.",
    +    "500.title": "Internal Error.",
         "500.message": "Упс! Изгледа да нешто ије како треба!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Регистрација",
         "login": "Пријава",
         "please_log_in": "Молимо, пријавите се",
    diff --git a/public/language/sv/global.json b/public/language/sv/global.json
    index 5c1b98522e..847c19a5c0 100644
    --- a/public/language/sv/global.json
    +++ b/public/language/sv/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Du kanske bör försöka logga in?",
         "404.title": "Sidan saknas",
         "404.message": "Du verkar ha ramlat in på en sida som inte finns. Återgå till första sidan.",
    -    "500.title": "Internt fel.",
    +    "500.title": "Internal Error.",
         "500.message": "Hoppsan! Något verkar ha gått fel!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Registrera",
         "login": "Logga in",
         "please_log_in": "Var god logga in",
    diff --git a/public/language/th/global.json b/public/language/th/global.json
    index eb81fb4ba5..4e4b7cccb7 100644
    --- a/public/language/th/global.json
    +++ b/public/language/th/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Perhaps you should try logging in?",
         "404.title": "ไม่พบ",
         "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
    -    "500.title": "มีข้อผิดพลาดภายในระบบ",
    +    "500.title": "Internal Error.",
         "500.message": "อุ่ย! มีสิ่งที่ไม่ถูกต้องเกิดขึ้น!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "ลงทะเบียน",
         "login": "เข้าสู่ระบบ",
         "please_log_in": "กรุณาเข้าสู่ระบบ",
    diff --git a/public/language/tr/global.json b/public/language/tr/global.json
    index 9e3c328dae..54801fc108 100644
    --- a/public/language/tr/global.json
    +++ b/public/language/tr/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Belki de tekrar giriş yapmayı denersiniz?",
         "404.title": "Bulunamadı",
         "404.message": "Erişim izniniz olmayan bir sayfaya denk gelmiş gibisiniz. Anasayfa'ya geri dönün.",
    -    "500.title": "Dahili hata.",
    +    "500.title": "Internal Error.",
         "500.message": "Ups! Bir şeyler ters gitti sanki!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Kayıt Ol",
         "login": "Giriş",
         "please_log_in": "Lütfen Giriş Yapınız",
    diff --git a/public/language/vi/global.json b/public/language/vi/global.json
    index 2cbb1536c8..1043051526 100644
    --- a/public/language/vi/global.json
    +++ b/public/language/vi/global.json
    @@ -7,8 +7,10 @@
         "403.login": "Có lẽ bạn nên thử đăng nhập?",
         "404.title": "Không tìm thấy",
         "404.message": "Có vẻ như bạn đang cố vào một trang không tồn tại. Hãy trở lại trang chủ.",
    -    "500.title": "Lỗi nội bộ",
    +    "500.title": "Internal Error.",
         "500.message": "Úi chà! Có vẻ như có trục trặc rồi!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "Đăng ký",
         "login": "Đăng nhập",
         "please_log_in": "Xin hãy đăng nhập",
    diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json
    index 5788c57c59..e3d3aa55d4 100644
    --- a/public/language/zh_CN/error.json
    +++ b/public/language/zh_CN/error.json
    @@ -20,7 +20,7 @@
         "email-taken": "此电子邮箱已被占用",
         "email-not-confirmed": "您的电子邮箱尚未确认,请点击这里确认您的电子邮箱。",
         "email-not-confirmed-chat": "您的电子邮箱尚未确认,无法聊天,请点击这里确认您的电子邮箱。",
    -    "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.",
    +    "email-not-confirmed-email-sent": "您的邮箱地址还没有被确认,请检查邮箱中的确认邮件。",
         "no-email-to-confirm": "本论坛需要电子邮箱确认,请点击这里输入电子邮箱地址",
         "email-confirm-failed": "我们无法确认您的电子邮箱,请重试",
         "confirm-email-already-sent": "确认邮件已发出,如需重新发送请等待 %1 分钟后再试。",
    @@ -55,8 +55,8 @@
         "post-delete-duration-expired-hours-minutes": "您只能在发表 %1 小时 %2 分钟后删除帖子",
         "post-delete-duration-expired-days": "您只能在发表 %1 天后删除帖子",
         "post-delete-duration-expired-days-hours": "您只能在发表 %1 天 %2 小时后删除帖子",
    -    "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply",
    -    "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies",
    +    "cant-delete-topic-has-reply": "您不能删除您的主题,因为已有回复。",
    +    "cant-delete-topic-has-replies": "您不能删除您的主题,因为已有 %1 条回复。",
         "content-too-short": "请增添发帖内容,不能少于 %1 个字符。",
         "content-too-long": "请删减发帖内容,不能超过 %1 个字符。",
         "title-too-short": "请增加标题,不能少于 %1 个字符。",
    @@ -123,5 +123,5 @@
         "no-users-in-room": "这个聊天室中没有用户",
         "cant-kick-self": "你不能把自己踢出群组",
         "no-users-selected": "尚未选择用户",
    -    "invalid-home-page-route": "Invalid home page route"
    +    "invalid-home-page-route": "无效的首页路径"
     }
    \ No newline at end of file
    diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json
    index 4be989cb9f..9d6a0d5a47 100644
    --- a/public/language/zh_CN/global.json
    +++ b/public/language/zh_CN/global.json
    @@ -7,8 +7,10 @@
         "403.login": "或许您应该先 登录试试?",
         "404.title": "未找到",
         "404.message": "您访问的页面不存在。返回首页。",
    -    "500.title": "内部错误。",
    +    "500.title": "内部错误",
         "500.message": "哎呀!看来是哪里出错了!",
    +    "400.title": "错误的请求",
    +    "400.message": "看起来这个链接的格式不正确,请再次检查并重试。或者返回主页。",
         "register": "注册",
         "login": "登录",
         "please_log_in": "请登录",
    @@ -93,5 +95,5 @@
         "upload_file": "上传文件",
         "upload": "上传",
         "allowed-file-types": "允许的文件类型有 %1",
    -    "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?"
    +    "unsaved-changes": "您有未保存的更改,您确定您要离开么?"
     }
    \ No newline at end of file
    diff --git a/public/language/zh_CN/login.json b/public/language/zh_CN/login.json
    index d9d5e10980..d46196e47d 100644
    --- a/public/language/zh_CN/login.json
    +++ b/public/language/zh_CN/login.json
    @@ -8,5 +8,5 @@
         "failed_login_attempt": "登录失败",
         "login_successful": "您已经成功登录!",
         "dont_have_account": "没有帐号?",
    -    "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity"
    +    "logged-out-due-to-inactivity": "由于长时间不活动,您已从控制面板注销"
     }
    \ No newline at end of file
    diff --git a/public/language/zh_CN/modules.json b/public/language/zh_CN/modules.json
    index 869a8bf0c0..b00e1c9aa4 100644
    --- a/public/language/zh_CN/modules.json
    +++ b/public/language/zh_CN/modules.json
    @@ -37,7 +37,7 @@
         "composer.formatting.picture": "图片",
         "composer.upload-picture": "上传图片",
         "composer.upload-file": "上传文件",
    -    "composer.zen_mode": "Zen Mode",
    +    "composer.zen_mode": "无干扰模式",
         "bootbox.ok": "确认",
         "bootbox.cancel": "取消",
         "bootbox.confirm": "确认",
    diff --git a/public/language/zh_CN/topic.json b/public/language/zh_CN/topic.json
    index 072da54e1a..0d4168ffdc 100644
    --- a/public/language/zh_CN/topic.json
    +++ b/public/language/zh_CN/topic.json
    @@ -26,8 +26,8 @@
         "tools": "工具",
         "flag": "举报",
         "locked": "已锁定",
    -    "pinned": "Pinned",
    -    "moved": "Moved",
    +    "pinned": "已固定",
    +    "moved": "已移动",
         "bookmark_instructions": "点击阅读本主题帖中的最新回复",
         "flag_title": "举报此帖",
         "flag_success": "已举报此回帖。",
    @@ -86,7 +86,7 @@
         "topic_will_be_moved_to": "此主题将被移动到版块",
         "fork_topic_instruction": "点击将分割的帖子",
         "fork_no_pids": "未选中帖子!",
    -    "fork_pid_count": "%1 post(s) selected",
    +    "fork_pid_count": "选择了 %1 个帖子",
         "fork_success": "成功分割主题! 点这里跳转到分割后的主题。",
         "delete_posts_instruction": "点击想要删除/永久删除的帖子",
         "composer.title_placeholder": "在此输入您主题的标题...",
    diff --git a/public/language/zh_TW/global.json b/public/language/zh_TW/global.json
    index d530c0ace1..f36d9ed09e 100644
    --- a/public/language/zh_TW/global.json
    +++ b/public/language/zh_TW/global.json
    @@ -7,8 +7,10 @@
         "403.login": "或許是你應該 試著登入?",
         "404.title": "無法找到該頁",
         "404.message": "你所查找的頁面並不存在。返回首頁。",
    -    "500.title": "內部錯誤",
    +    "500.title": "Internal Error.",
         "500.message": "糟糕! 看來是不知道哪裡出錯了!",
    +    "400.title": "Bad Request.",
    +    "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.",
         "register": "註冊",
         "login": "登入",
         "please_log_in": "請先登入",
    
    From 5c830758061bd333e2dd9105c4a18816916e55ef Mon Sep 17 00:00:00 2001
    From: barisusakli 
    Date: Fri, 26 Aug 2016 16:39:03 +0300
    Subject: [PATCH 129/386] moved variables parse to ajaxify.js
    
    ---
     public/src/ajaxify.js   | 10 ++++++++--
     public/src/app.js       |  1 +
     public/src/variables.js | 15 ---------------
     src/meta/js.js          |  1 -
     4 files changed, 9 insertions(+), 18 deletions(-)
     delete mode 100644 public/src/variables.js
    
    diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
    index 8237a9961b..87122bf78c 100644
    --- a/public/src/ajaxify.js
    +++ b/public/src/ajaxify.js
    @@ -198,8 +198,6 @@ $(document).ready(function() {
     		}
     		var count = 2;
     
    -		ajaxify.variables.parse();
    -
     		ajaxify.loadScript(tpl_url, done);
     
     		ajaxify.widgets.render(tpl_url, url, done);
    @@ -209,6 +207,14 @@ $(document).ready(function() {
     		app.processPage();
     	};
     
    +	ajaxify.parseData = function() {
    +		var dataEl = $('#ajaxify-data');
    +		if (dataEl.length) {
    +			ajaxify.data = JSON.parse(dataEl.text());
    +			dataEl.remove();
    +		}
    +	};
    +
     	ajaxify.removeRelativePath = function(url) {
     		if (url.startsWith(RELATIVE_PATH.slice(1))) {
     			url = url.slice(RELATIVE_PATH.length);
    diff --git a/public/src/app.js b/public/src/app.js
    index e66d0d03c2..b1896e071a 100644
    --- a/public/src/app.js
    +++ b/public/src/app.js
    @@ -24,6 +24,7 @@ app.cacheBuster = null;
     
     		var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search + window.location.hash);
     		ajaxify.updateHistory(url, true);
    +		ajaxify.parseData();
     		ajaxify.end(url, app.template);
     
     		handleStatusChange();
    diff --git a/public/src/variables.js b/public/src/variables.js
    deleted file mode 100644
    index 487099ac3c..0000000000
    --- a/public/src/variables.js
    +++ /dev/null
    @@ -1,15 +0,0 @@
    -"use strict";
    -/*global ajaxify*/
    -
    -(function(ajaxify) {
    -
    -	ajaxify.variables = {};
    -
    -	ajaxify.variables.parse = function() {
    -		var dataEl = $('#ajaxify-data');
    -		if (dataEl.length) {
    -			ajaxify.data = JSON.parse(dataEl.text());
    -			dataEl.remove();
    -		}
    -	};
    -}(ajaxify || {}));
    diff --git a/src/meta/js.js b/src/meta/js.js
    index ffd66f010e..848ee16e6f 100644
    --- a/src/meta/js.js
    +++ b/src/meta/js.js
    @@ -37,7 +37,6 @@ module.exports = function(Meta) {
     				'public/src/app.js',
     				'public/src/ajaxify.js',
     				'public/src/overrides.js',
    -				'public/src/variables.js',
     				'public/src/widgets.js'
     			],
     
    
    From 3c1a4876d72724506da31993557c2fce714614b6 Mon Sep 17 00:00:00 2001
    From: barisusakli 
    Date: Fri, 26 Aug 2016 17:01:11 +0300
    Subject: [PATCH 130/386] removed unused vars
    
    ---
     public/src/client/infinitescroll.js | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js
    index 42443aa27e..ed21602b01 100644
    --- a/public/src/client/infinitescroll.js
    +++ b/public/src/client/infinitescroll.js
    @@ -1,8 +1,8 @@
     'use strict';
     
    -/* globals define, socket, ajaxify, templates, app */
    +/* globals define, socket, app */
     
    -define('forum/infinitescroll', ['translator'], function(translator) {
    +define('forum/infinitescroll', function() {
     
     	var scroll = {};
     	var callback;
    
    From 91980089071c5c603dd3494007059d45d12af22f Mon Sep 17 00:00:00 2001
    From: Julian Lam 
    Date: Fri, 26 Aug 2016 09:20:44 -0400
    Subject: [PATCH 131/386] forcing ACP settingsv1 to wait a beat before firing
     client-side hook so client-side scripts can register listeners first
    
    ---
     public/src/admin/settings.js | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js
    index 9e616cfe80..bf6a0b4130 100644
    --- a/public/src/admin/settings.js
    +++ b/public/src/admin/settings.js
    @@ -109,7 +109,9 @@ define('admin/settings', ['uploader'], function(uploader) {
     			callback();
     		}
     
    -		$(window).trigger('action:admin.settingsLoaded');
    +		setTimeout(function() {
    +			$(window).trigger('action:admin.settingsLoaded');
    +		}, 0);
     	};
     
     	function handleUploads() {
    
    From e9770b6bd5b75eae1444ac32c58e8a419a1898cb Mon Sep 17 00:00:00 2001
    From: Julian Lam 
    Date: Fri, 26 Aug 2016 09:39:13 -0400
    Subject: [PATCH 132/386] firing client-side hook when settingsv1 saves
    
    ---
     public/src/admin/settings.js | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js
    index bf6a0b4130..d6436ccefc 100644
    --- a/public/src/admin/settings.js
    +++ b/public/src/admin/settings.js
    @@ -93,6 +93,8 @@ define('admin/settings', ['uploader'], function(uploader) {
     					message: 'Your changes to the NodeBB configuration have been saved.',
     					type: 'success'
     				});
    +
    +				$(window).trigger('action:admin.settingsSaved');
     			});
     		});
     
    
    From 25cd772e0efcf027ac373c0a30261a634d251bd8 Mon Sep 17 00:00:00 2001
    From: Julian Lam 
    Date: Fri, 26 Aug 2016 10:04:38 -0400
    Subject: [PATCH 133/386] closes #4766
    
    ---
     public/src/admin/settings/email.js | 30 ++++++++++++++
     src/socket.io/admin/user.js        |  3 ++
     src/socket.io/meta.js              |  5 ++-
     src/user/jobs.js                   | 64 ++++++++++++++++++++++++------
     src/views/admin/settings/email.tpl | 15 ++++++-
     5 files changed, 102 insertions(+), 15 deletions(-)
    
    diff --git a/public/src/admin/settings/email.js b/public/src/admin/settings/email.js
    index e6015b3b32..78a058e19d 100644
    --- a/public/src/admin/settings/email.js
    +++ b/public/src/admin/settings/email.js
    @@ -8,6 +8,11 @@ define('admin/settings/email', ['admin/settings'], function(settings) {
     	module.init = function() {
     		configureEmailTester();
     		configureEmailEditor();
    +
    +		$(window).on('action:admin.settingsLoaded action:admin.settingsSaved', handleDigestHourChange);
    +		$(window).on('action:admin.settingsSaved', function() {
    +			socket.emit('admin.user.restartJobs');
    +		});
     	};
     
     	function configureEmailTester() {
    @@ -57,5 +62,30 @@ define('admin/settings/email', ['admin/settings'], function(settings) {
     		});
     	}
     
    +	function handleDigestHourChange() {
    +		var hour = parseInt($('#digestHour').val(), 10);
    +
    +		if (isNaN(hour)) {
    +			hour = 17;
    +		} else if (hour > 23 || hour < 0) {
    +			hour = 0;
    +		}
    +
    +		socket.emit('meta.getServerTime', {}, function(err, now) {
    +			now = new Date(now);
    +
    +			$('#serverTime').text(now.toString());
    +
    +			now.setHours(parseInt(hour, 10), 0, 0, 0);
    +
    +			// If adjusted time is in the past, move to next day
    +			if (now.getTime() < Date.now()) {
    +				now.setDate(now.getDate() + 1);
    +			}
    +
    +			$('#nextDigestTime').text(now.toString());
    +		});
    +	}
    +
     	return module;
     });
    diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js
    index 3fd69a8d10..42dcd23266 100644
    --- a/src/socket.io/admin/user.js
    +++ b/src/socket.io/admin/user.js
    @@ -226,5 +226,8 @@ User.rejectRegistration = function(socket, data, callback) {
     	user.rejectRegistration(data.username, callback);
     };
     
    +User.restartJobs = function(socket, data, callback) {
    +	user.startJobs(callback);
    +};
     
     module.exports = User;
    diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js
    index 39603c8d0c..e5c114f372 100644
    --- a/src/socket.io/meta.js
    +++ b/src/socket.io/meta.js
    @@ -68,6 +68,9 @@ function leaveCurrentRoom(socket) {
     	}
     }
     
    -
    +SocketMeta.getServerTime = function(socket, data, callback) {
    +	// Returns server time in milliseconds
    +	callback(null, Date.now());
    +};
     
     module.exports = SocketMeta;
    diff --git a/src/user/jobs.js b/src/user/jobs.js
    index 16f6bd4ce8..9c9569dc40 100644
    --- a/src/user/jobs.js
    +++ b/src/user/jobs.js
    @@ -1,29 +1,69 @@
    -
     'use strict';
     
    -var winston = require('winston'),
    -	cronJob = require('cron').CronJob,
    +var winston = require('winston');
    +var cronJob = require('cron').CronJob;
     
    -	meta = require('../meta');
    +var meta = require('../meta');
    +
    +var jobs = {};
     
     module.exports = function(User) {
    -	User.startJobs = function() {
    -		new cronJob('0 0 17 * * *', function() {
    -			winston.verbose('[user.startJobs] Digest job (daily) started.');
    +	User.startJobs = function(callback) {
    +		winston.verbose('[user/jobs] (Re-)starting user jobs...');
    +		var terminated = 0;
    +		var started = 0;
    +		var digestHour = parseInt(meta.config.digestHour, 10);
    +
    +		// Fix digest hour if invalid
    +		if (isNaN(digestHour)) {
    +			digestHour = 17;
    +		} else if (digestHour > 23 || digestHour < 0) {
    +			digestHour = 0;
    +		}
    +
    +		// Terminate any active cron jobs
    +		for(var jobId in jobs) {
    +			if (jobs.hasOwnProperty(jobId)) {
    +				winston.verbose('[user/jobs] Terminating job (' + jobId + ')');
    +				jobs[jobId].stop();
    +				delete jobs[jobId];
    +				++terminated;
    +			}
    +		}
    +		winston.verbose('[user/jobs] ' + terminated + ' jobs terminated');
    +
    +		jobs['digest.daily'] = new cronJob('0 0 ' + digestHour + ' * * *', function() {
    +			winston.verbose('[user/jobs] Digest job (daily) started.');
     			User.digest.execute('day');
     		}, null, true);
    +		winston.verbose('[user/jobs] Starting job (digest.daily)');
    +		++started;
     
    -		new cronJob('0 0 17 * * 0', function() {
    -			winston.verbose('[user.startJobs] Digest job (weekly) started.');
    +		jobs['digest.weekly'] = new cronJob('0 0 ' + digestHour + ' * * 0', function() {
    +			winston.verbose('[user/jobs] Digest job (weekly) started.');
     			User.digest.execute('week');
     		}, null, true);
    +		winston.verbose('[user/jobs] Starting job (digest.weekly)');
    +		++started;
     
    -		new cronJob('0 0 17 1 * *', function() {
    -			winston.verbose('[user.startJobs] Digest job (monthly) started.');
    +		jobs['digest.monthly'] = new cronJob('0 0 ' + digestHour + ' 1 * *', function() {
    +			winston.verbose('[user/jobs] Digest job (monthly) started.');
     			User.digest.execute('month');
     		}, null, true);
    +		winston.verbose('[user/jobs] Starting job (digest.monthly)');
    +		++started;
     
    -		new cronJob('0 0 0 * * *', User.reset.clean, null, true);
    +		jobs['reset.clean'] = new cronJob('0 0 0 * * *', User.reset.clean, null, true);
    +		winston.verbose('[user/jobs] Starting job (reset.clean)');
    +		++started;
    +
    +		winston.verbose('[user/jobs] ' + started + ' jobs started');
    +
    +		if (typeof callback === 'function') {
    +			callback();
    +		}
    +
    +		return;
     	};
     };
     
    diff --git a/src/views/admin/settings/email.tpl b/src/views/admin/settings/email.tpl
    index cb49aa17b5..3b65cbc0a4 100644
    --- a/src/views/admin/settings/email.tpl
    +++ b/src/views/admin/settings/email.tpl
    @@ -34,14 +34,14 @@
     			
     			
    -
    +

    Enter the full email address here, especially if you are using a Google Apps managed domain.

    -
    +
    @@ -92,6 +92,17 @@ Disable subscriber notification emails + +
    + + +

    + Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). + Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
    + The approximate server time is:
    + The next daily digest is scheduled to be sent +

    +
    From f5941cbf1c8b542bfe8a0c10fd969a5e9139cc5d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 17:14:08 +0300 Subject: [PATCH 134/386] removed unused requires --- src/meta.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/meta.js b/src/meta.js index 8dfbc0d99a..2aca05f308 100644 --- a/src/meta.js +++ b/src/meta.js @@ -1,18 +1,14 @@ "use strict"; -var async = require('async'), - winston = require('winston'), - templates = require('templates.js'), - os = require('os'), - nconf = require('nconf'), +var async = require('async'); +var winston = require('winston'); - user = require('./user'), - groups = require('./groups'), - languages = require('./languages'), - emitter = require('./emitter'), - pubsub = require('./pubsub'), - auth = require('./routes/authentication'), - utils = require('../public/src/utils'); +var os = require('os'); +var nconf = require('nconf'); +var user = require('./user'); +var groups = require('./groups'); +var pubsub = require('./pubsub'); +var utils = require('../public/src/utils'); (function (Meta) { Meta.reloadRequired = false; From 8f408faf4649ff87a4e6491e2473fbfa5f2efe16 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 18:50:37 +0300 Subject: [PATCH 135/386] organize middlewares removed app.locals.middleware middlewares can be required anywhere, ie in controllers --- src/controllers/index.js | 10 +- src/meta/configs.js | 11 +- src/middleware/admin.js | 205 +++++++++---------- src/middleware/header.js | 6 +- src/middleware/headers.js | 51 +++++ src/middleware/index.js | 259 ++++++++++++++++------- src/middleware/middleware.js | 384 ----------------------------------- src/middleware/user.js | 154 ++++++++++++++ src/plugins.js | 4 +- src/webserver.js | 109 ++++++++-- 10 files changed, 602 insertions(+), 591 deletions(-) create mode 100644 src/middleware/headers.js delete mode 100644 src/middleware/middleware.js create mode 100644 src/middleware/user.js diff --git a/src/controllers/index.js b/src/controllers/index.js index 975dca54ae..d39352e6d0 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -376,8 +376,8 @@ Controllers.handle404 = function(req, res) { if (res.locals.isAPI) { return res.json({path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]'}); } - - req.app.locals.middleware.buildHeader(req, res, function() { + var middleware = require('../middleware'); + middleware.buildHeader(req, res, function() { res.render('404', {path: validator.escape(path), title: '[[global:404.title]]'}); }); } else { @@ -402,7 +402,8 @@ Controllers.handleURIErrors = function(err, req, res, next) { error: '[[global:400.title]]' }); } else { - req.app.locals.middleware.buildHeader(req, res, function() { + var middleware = require('../middleware'); + middleware.buildHeader(req, res, function() { res.render('400', { error: validator.escape(String(err.message)) }); }); } @@ -435,7 +436,8 @@ Controllers.handleErrors = function(err, req, res, next) { if (res.locals.isAPI) { res.json({path: validator.escape(path), error: err.message}); } else { - req.app.locals.middleware.buildHeader(req, res, function() { + var middleware = require('../middleware'); + middleware.buildHeader(req, res, function() { res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) }); }); } diff --git a/src/meta/configs.js b/src/meta/configs.js index 69cc375b85..61464c232a 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -1,11 +1,12 @@ 'use strict'; -var winston = require('winston'), - db = require('../database'), - pubsub = require('../pubsub'), - nconf = require('nconf'), - utils = require('../../public/src/utils'); +var winston = require('winston'); +var nconf = require('nconf'); + +var db = require('../database'); +var pubsub = require('../pubsub'); +var utils = require('../../public/src/utils'); module.exports = function(Meta) { diff --git a/src/middleware/admin.js b/src/middleware/admin.js index 2ee0f7fd80..5e4399f226 100644 --- a/src/middleware/admin.js +++ b/src/middleware/admin.js @@ -1,130 +1,125 @@ "use strict"; -var app, - middleware = {}, - nconf = require('nconf'), - async = require('async'), - winston = require('winston'), - user = require('../user'), - meta = require('../meta'), - plugins = require('../plugins'), +var async = require('async'); +var winston = require('winston'); +var user = require('../user'); +var meta = require('../meta'); +var plugins = require('../plugins'); - controllers = { - api: require('../controllers/api'), - helpers: require('../controllers/helpers') - }; - -middleware.isAdmin = function(req, res, next) { - winston.warn('[middleware.admin.isAdmin] deprecation warning, no need to use this from plugins!'); - - if (!req.user) { - return controllers.helpers.notAllowed(req, res); - } - - user.isAdministrator(req.user.uid, function (err, isAdmin) { - if (err || isAdmin) { - return next(err); - } - - controllers.helpers.notAllowed(req, res); - }); +var controllers = { + api: require('../controllers/api'), + helpers: require('../controllers/helpers') }; -middleware.buildHeader = function(req, res, next) { - res.locals.renderAdminHeader = true; +module.exports = function(middleware) { + middleware.admin = {}; + middleware.admin.isAdmin = function(req, res, next) { + winston.warn('[middleware.admin.isAdmin] deprecation warning, no need to use this from plugins!'); - async.parallel({ - config: function(next) { - controllers.api.getConfig(req, res, next); - }, - footer: function(next) { - app.render('admin/footer', {}, next); - } - }, function(err, results) { - if (err) { - return next(err); + if (!req.user) { + return controllers.helpers.notAllowed(req, res); } - res.locals.config = results.config; - res.locals.adminFooter = results.footer; - next(); - }); -}; + user.isAdministrator(req.user.uid, function (err, isAdmin) { + if (err || isAdmin) { + return next(err); + } -middleware.renderHeader = function(req, res, data, next) { - var custom_header = { - 'plugins': [], - 'authentication': [] + controllers.helpers.notAllowed(req, res); + }); }; - user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], function(err, userData) { - if (err) { - return next(err); - } - - userData.uid = req.uid; - userData['email:confirmed'] = parseInt(userData['email:confirmed'], 10) === 1; + middleware.admin.buildHeader = function(req, res, next) { + res.locals.renderAdminHeader = true; async.parallel({ - scripts: function(next) { - plugins.fireHook('filter:admin.scripts.get', [], function(err, scripts) { - if (err) { - return next(err); - } - var arr = []; - scripts.forEach(function(script) { - arr.push({src: script}); - }); - - next(null, arr); - }); - }, - custom_header: function(next) { - plugins.fireHook('filter:admin.header.build', custom_header, next); - }, config: function(next) { controllers.api.getConfig(req, res, next); }, - configs: function(next) { - meta.configs.list(next); + footer: function(next) { + req.app.render('admin/footer', {}, next); } }, function(err, results) { if (err) { return next(err); } + res.locals.config = results.config; - - var acpPath = req.path.slice(1).split('/'); - acpPath.forEach(function(path, i) { - acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); - }); - acpPath = acpPath.join(' > '); - - var templateValues = { - config: results.config, - configJSON: JSON.stringify(results.config), - relative_path: results.config.relative_path, - adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), - user: userData, - userJSON: JSON.stringify(userData).replace(/'/g, "\\'"), - plugins: results.custom_header.plugins, - authentication: results.custom_header.authentication, - scripts: results.scripts, - 'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '', - env: process.env.NODE_ENV ? true : false, - title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel', - bodyClass: data.bodyClass - }; - - templateValues.template = {name: res.locals.template}; - templateValues.template[res.locals.template] = true; - - app.render('admin/header', templateValues, next); + res.locals.adminFooter = results.footer; + next(); }); - }); -}; + }; -module.exports = function(webserver) { - app = webserver; - return middleware; + middleware.admin.renderHeader = function(req, res, data, next) { + var custom_header = { + 'plugins': [], + 'authentication': [] + }; + + user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], function(err, userData) { + if (err) { + return next(err); + } + + userData.uid = req.uid; + userData['email:confirmed'] = parseInt(userData['email:confirmed'], 10) === 1; + + async.parallel({ + scripts: function(next) { + plugins.fireHook('filter:admin.scripts.get', [], function(err, scripts) { + if (err) { + return next(err); + } + var arr = []; + scripts.forEach(function(script) { + arr.push({src: script}); + }); + + next(null, arr); + }); + }, + custom_header: function(next) { + plugins.fireHook('filter:admin.header.build', custom_header, next); + }, + config: function(next) { + controllers.api.getConfig(req, res, next); + }, + configs: function(next) { + meta.configs.list(next); + } + }, function(err, results) { + if (err) { + return next(err); + } + res.locals.config = results.config; + + var acpPath = req.path.slice(1).split('/'); + acpPath.forEach(function(path, i) { + acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); + }); + acpPath = acpPath.join(' > '); + + var templateValues = { + config: results.config, + configJSON: JSON.stringify(results.config), + relative_path: results.config.relative_path, + adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), + user: userData, + userJSON: JSON.stringify(userData).replace(/'/g, "\\'"), + plugins: results.custom_header.plugins, + authentication: results.custom_header.authentication, + scripts: results.scripts, + 'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '', + env: process.env.NODE_ENV ? true : false, + title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel', + bodyClass: data.bodyClass + }; + + templateValues.template = {name: res.locals.template}; + templateValues.template[res.locals.template] = true; + + req.app.render('admin/header', templateValues, next); + }); + }); + }; }; diff --git a/src/middleware/header.js b/src/middleware/header.js index 6425c17d74..b4013d1481 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -16,7 +16,7 @@ var controllers = { helpers: require('../controllers/helpers') }; -module.exports = function(app, middleware) { +module.exports = function(middleware) { middleware.buildHeader = function(req, res, next) { res.locals.renderHeader = true; @@ -28,7 +28,7 @@ module.exports = function(app, middleware) { controllers.api.getConfig(req, res, next); }, footer: function(next) { - app.render('footer', { + req.app.render('footer', { loggedIn: !!req.uid, title: validator.escape(meta.config.title || meta.config.browserTitle || 'NodeBB') }, next); @@ -160,7 +160,7 @@ module.exports = function(app, middleware) { return callback(err); } - app.render('header', data.templateValues, callback); + req.app.render('header', data.templateValues, callback); }); }); }; diff --git a/src/middleware/headers.js b/src/middleware/headers.js new file mode 100644 index 0000000000..39c9520e59 --- /dev/null +++ b/src/middleware/headers.js @@ -0,0 +1,51 @@ +'use strict'; + + +var meta = require('../meta'); +var _ = require('underscore'); + + +module.exports = function(middleware) { + + middleware.addHeaders = function (req, res, next) { + var defaults = { + 'X-Powered-By': 'NodeBB', + 'X-Frame-Options': 'SAMEORIGIN', + 'Access-Control-Allow-Origin': 'null' // yes, string null. + }; + var headers = { + 'X-Powered-By': meta.config['powered-by'], + 'X-Frame-Options': meta.config['allow-from-uri'] ? 'ALLOW-FROM ' + meta.config['allow-from-uri'] : undefined, + 'Access-Control-Allow-Origin': meta.config['access-control-allow-origin'], + 'Access-Control-Allow-Methods': meta.config['access-control-allow-methods'], + 'Access-Control-Allow-Headers': meta.config['access-control-allow-headers'] + }; + + _.defaults(headers, defaults); + headers = _.pick(headers, Boolean); // Remove falsy headers + + for(var key in headers) { + if (headers.hasOwnProperty(key)) { + res.setHeader(key, headers[key]); + } + } + + next(); + }; + + middleware.addExpiresHeaders = function(req, res, next) { + if (req.app.enabled('cache')) { + res.setHeader("Cache-Control", "public, max-age=5184000"); + res.setHeader("Expires", new Date(Date.now() + 5184000000).toUTCString()); + } else { + res.setHeader("Cache-Control", "public, max-age=0"); + res.setHeader("Expires", new Date().toUTCString()); + } + + next(); + }; + +}; + + + diff --git a/src/middleware/index.js b/src/middleware/index.js index 867af6fed2..266f2b1488 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -1,83 +1,202 @@ "use strict"; -var meta = require('../meta'), - db = require('../database'), - file = require('../file'), - auth = require('../routes/authentication'), +var async = require('async'); +var fs = require('fs'); +var path = require('path'); +var csrf = require('csurf'); +var validator = require('validator'); +var nconf = require('nconf'); +var ensureLoggedIn = require('connect-ensure-login'); +var toobusy = require('toobusy-js'); - path = require('path'), - nconf = require('nconf'), - flash = require('connect-flash'), - templates = require('templates.js'), - bodyParser = require('body-parser'), - cookieParser = require('cookie-parser'), - compression = require('compression'), - favicon = require('serve-favicon'), - session = require('express-session'), - useragent = require('express-useragent'); +var plugins = require('../plugins'); +var languages = require('../languages'); +var meta = require('../meta'); +var user = require('../user'); +var groups = require('../groups'); +var analytics = require('../analytics'); + +var controllers = { + api: require('./../controllers/api'), + helpers: require('../controllers/helpers') +}; var middleware = {}; -function setupFavicon(app) { - var faviconPath = path.join(__dirname, '../../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico'); - if (file.existsSync(faviconPath)) { - app.use(nconf.get('relative_path'), favicon(faviconPath)); +middleware.applyCSRF = csrf(); + +middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login'); + +require('./admin')(middleware); +require('./header')(middleware); +require('./render')(middleware); +require('./maintenance')(middleware); +require('./user')(middleware); +require('./headers')(middleware); + +middleware.authenticate = function(req, res, next) { + if (req.user) { + return next(); + } else if (plugins.hasListeners('action:middleware.authenticate')) { + return plugins.fireHook('action:middleware.authenticate', { + req: req, + res: res, + next: next + }); } + + controllers.helpers.notAllowed(req, res); +}; + +middleware.pageView = function(req, res, next) { + analytics.pageView({ + ip: req.ip, + path: req.path, + uid: req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') ? parseInt(req.user.uid, 10) : 0 + }); + + plugins.fireHook('action:middleware.pageView', {req: req}); + + if (req.user) { + user.updateLastOnlineTime(req.user.uid); + if (req.path.startsWith('/api/users') || req.path.startsWith('/users')) { + user.updateOnlineUsers(req.user.uid, next); + } else { + user.updateOnlineUsers(req.user.uid); + next(); + } + } else { + next(); + } +}; + + +middleware.pluginHooks = function(req, res, next) { + async.each(plugins.loadedHooks['filter:router.page'] || [], function(hookObj, next) { + hookObj.method(req, res, next); + }, function() { + // If it got here, then none of the subscribed hooks did anything, or there were no hooks + next(); + }); +}; + +middleware.validateFiles = function(req, res, next) { + if (!Array.isArray(req.files.files) || !req.files.files.length) { + return next(new Error(['[[error:invalid-files]]'])); + } + + next(); +}; + +middleware.prepareAPI = function(req, res, next) { + res.locals.isAPI = true; + next(); +}; + +middleware.routeTouchIcon = function(req, res) { + if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) { + return res.redirect(meta.config['brand:touchIcon']); + } else { + return res.sendFile(path.join(__dirname, '../../public', meta.config['brand:touchIcon'] || '/logo.png'), { + maxAge: req.app.enabled('cache') ? 5184000000 : 0 + }); + } +}; + +middleware.privateTagListing = function(req, res, next) { + if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) { + controllers.helpers.notAllowed(req, res); + } else { + next(); + } +}; + +middleware.exposeGroupName = function(req, res, next) { + expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next); +}; + +middleware.exposeUid = function(req, res, next) { + expose('uid', user.getUidByUserslug, 'userslug', req, res, next); +}; + +function expose(exposedField, method, field, req, res, next) { + if (!req.params.hasOwnProperty(field)) { + return next(); + } + method(req.params[field], function(err, id) { + if (err) { + return next(err); + } + + res.locals[exposedField] = id; + next(); + }); } -module.exports = function(app) { - var relativePath = nconf.get('relative_path'); - - middleware = require('./middleware')(app); - - app.engine('tpl', templates.__express); - app.set('view engine', 'tpl'); - app.set('views', nconf.get('views_dir')); - app.set('json spaces', process.env.NODE_ENV === 'development' ? 4 : 0); - app.use(flash()); - - app.enable('view cache'); - - app.use(compression()); - - setupFavicon(app); - - app.use(relativePath + '/apple-touch-icon', middleware.routeTouchIcon); - - app.use(bodyParser.urlencoded({extended: true})); - app.use(bodyParser.json()); - app.use(cookieParser()); - app.use(useragent.express()); - - var cookie = { - maxAge: 1000 * 60 * 60 * 24 * (parseInt(meta.config.loginDays, 10) || 14) - }; - - if (meta.config.cookieDomain) { - cookie.domain = meta.config.cookieDomain; +middleware.privateUploads = function(req, res, next) { + if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) { + return next(); } - - if (nconf.get('secure')) { - cookie.secure = true; + if (req.path.startsWith('/uploads/files')) { + return res.status(403).json('not-allowed'); } - - if (relativePath !== '') { - cookie.path = relativePath; - } - - app.use(session({ - store: db.sessionStore, - secret: nconf.get('secret'), - key: 'express.sid', - cookie: cookie, - resave: true, - saveUninitialized: true - })); - - app.use(middleware.addHeaders); - app.use(middleware.processRender); - auth.initialize(app, middleware); - - return middleware; + next(); }; + +middleware.busyCheck = function(req, res, next) { + if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) { + analytics.increment('errors:503'); + res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html')); + } else { + next(); + } +}; + +middleware.applyBlacklist = function(req, res, next) { + meta.blacklist.test(req.ip, function(err) { + next(err); + }); +}; + +middleware.processLanguages = function(req, res, next) { + var code = req.params.code; + var key = req.path.match(/[\w]+\.json/); + + if (code && key) { + languages.get(code, key[0], function(err, language) { + if (err) { + return next(err); + } + + res.status(200).json(language); + }); + } else { + res.status(404).json('{}'); + } +}; + +middleware.processTimeagoLocales = function(req, res, next) { + var fallback = req.path.indexOf('-short') === -1 ? 'jquery.timeago.en.js' : 'jquery.timeago.en-short.js', + localPath = path.join(__dirname, '../../public/vendor/jquery/timeago/locales', req.path), + exists; + + try { + exists = fs.accessSync(localPath, fs.F_OK | fs.R_OK); + } catch(e) { + exists = false; + } + + if (exists) { + res.status(200).sendFile(localPath, { + maxAge: req.app.enabled('cache') ? 5184000000 : 0 + }); + } else { + res.status(200).sendFile(path.join(__dirname, '../../public/vendor/jquery/timeago/locales', fallback), { + maxAge: req.app.enabled('cache') ? 5184000000 : 0 + }); + } +}; + + +module.exports = middleware; diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js deleted file mode 100644 index e5caf9ba00..0000000000 --- a/src/middleware/middleware.js +++ /dev/null @@ -1,384 +0,0 @@ -"use strict"; - -var app; -var middleware = { - admin: {} -}; -var async = require('async'); -var fs = require('fs'); -var path = require('path'); -var csrf = require('csurf'); -var _ = require('underscore'); - -var validator = require('validator'); -var nconf = require('nconf'); -var ensureLoggedIn = require('connect-ensure-login'); -var toobusy = require('toobusy-js'); - -var plugins = require('../plugins'); -var languages = require('../languages'); -var meta = require('../meta'); -var user = require('../user'); -var groups = require('../groups'); - -var analytics = require('../analytics'); - -var controllers = { - api: require('./../controllers/api'), - helpers: require('../controllers/helpers') -}; - -toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100); -toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500); - -middleware.authenticate = function(req, res, next) { - if (req.user) { - return next(); - } else if (plugins.hasListeners('action:middleware.authenticate')) { - return plugins.fireHook('action:middleware.authenticate', { - req: req, - res: res, - next: next - }); - } - - controllers.helpers.notAllowed(req, res); -}; - -middleware.applyCSRF = csrf(); - -middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login'); - -middleware.pageView = function(req, res, next) { - analytics.pageView({ - ip: req.ip, - path: req.path, - uid: req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') ? parseInt(req.user.uid, 10) : 0 - }); - - plugins.fireHook('action:middleware.pageView', {req: req}); - - if (req.user) { - user.updateLastOnlineTime(req.user.uid); - if (req.path.startsWith('/api/users') || req.path.startsWith('/users')) { - user.updateOnlineUsers(req.user.uid, next); - } else { - user.updateOnlineUsers(req.user.uid); - next(); - } - } else { - next(); - } -}; - -middleware.addHeaders = function (req, res, next) { - var defaults = { - 'X-Powered-By': 'NodeBB', - 'X-Frame-Options': 'SAMEORIGIN', - 'Access-Control-Allow-Origin': 'null' // yes, string null. - }; - var headers = { - 'X-Powered-By': meta.config['powered-by'], - 'X-Frame-Options': meta.config['allow-from-uri'] ? 'ALLOW-FROM ' + meta.config['allow-from-uri'] : undefined, - 'Access-Control-Allow-Origin': meta.config['access-control-allow-origin'], - 'Access-Control-Allow-Methods': meta.config['access-control-allow-methods'], - 'Access-Control-Allow-Headers': meta.config['access-control-allow-headers'] - }; - - _.defaults(headers, defaults); - headers = _.pick(headers, Boolean); // Remove falsy headers - - for(var key in headers) { - if (headers.hasOwnProperty(key)) { - res.setHeader(key, headers[key]); - } - } - - next(); -}; - -middleware.pluginHooks = function(req, res, next) { - async.each(plugins.loadedHooks['filter:router.page'] || [], function(hookObj, next) { - hookObj.method(req, res, next); - }, function() { - // If it got here, then none of the subscribed hooks did anything, or there were no hooks - next(); - }); -}; - -middleware.redirectToAccountIfLoggedIn = function(req, res, next) { - if (req.session.forceLogin) { - return next(); - } - - if (!req.user) { - return next(); - } - user.getUserField(req.user.uid, 'userslug', function (err, userslug) { - if (err) { - return next(err); - } - controllers.helpers.redirect(res, '/user/' + userslug); - }); -}; - -middleware.validateFiles = function(req, res, next) { - if (!Array.isArray(req.files.files) || !req.files.files.length) { - return next(new Error(['[[error:invalid-files]]'])); - } - - next(); -}; - -middleware.prepareAPI = function(req, res, next) { - res.locals.isAPI = true; - next(); -}; - -middleware.checkGlobalPrivacySettings = function(req, res, next) { - if (!req.user && !!parseInt(meta.config.privateUserInfo, 10)) { - return controllers.helpers.notAllowed(req, res); - } - - next(); -}; - -middleware.checkAccountPermissions = function(req, res, next) { - // This middleware ensures that only the requested user and admins can pass - async.waterfall([ - function (next) { - middleware.authenticate(req, res, next); - }, - function (next) { - user.getUidByUserslug(req.params.userslug, next); - }, - function (uid, next) { - if (parseInt(uid, 10) === req.uid) { - return next(null, true); - } - - user.isAdminOrGlobalMod(req.uid, next); - } - ], function (err, allowed) { - if (err || allowed) { - return next(err); - } - controllers.helpers.notAllowed(req, res); - }); -}; - -middleware.redirectUidToUserslug = function(req, res, next) { - var uid = parseInt(req.params.uid, 10); - if (!uid) { - return next(); - } - user.getUserField(uid, 'userslug', function(err, userslug) { - if (err || !userslug) { - return next(err); - } - - var path = req.path.replace(/^\/api/, '') - .replace('uid', 'user') - .replace(uid, function() { return userslug; }); - controllers.helpers.redirect(res, path); - }); -}; - -middleware.isAdmin = function(req, res, next) { - if (!req.uid) { - return controllers.helpers.notAllowed(req, res); - } - - user.isAdministrator(req.uid, function (err, isAdmin) { - if (err) { - return next(err); - } - - if (isAdmin) { - user.hasPassword(req.uid, function(err, hasPassword) { - if (err) { - return next(err); - } - - if (!hasPassword) { - return next(); - } - - var loginTime = req.session.meta ? req.session.meta.datetime : 0; - if (loginTime && parseInt(loginTime, 10) > Date.now() - 3600000) { - var timeLeft = parseInt(loginTime, 10) - (Date.now() - 3600000); - if (timeLeft < 300000) { - req.session.meta.datetime += 300000; - } - - return next(); - } - - req.session.returnTo = req.path.replace(/^\/api/, ''); - req.session.forceLogin = 1; - if (res.locals.isAPI) { - res.status(401).json({}); - } else { - res.redirect(nconf.get('relative_path') + '/login'); - } - }); - return; - } - - if (res.locals.isAPI) { - return controllers.helpers.notAllowed(req, res); - } - - middleware.buildHeader(req, res, function() { - controllers.helpers.notAllowed(req, res); - }); - }); -}; - -middleware.routeTouchIcon = function(req, res) { - if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) { - return res.redirect(meta.config['brand:touchIcon']); - } else { - return res.sendFile(path.join(__dirname, '../../public', meta.config['brand:touchIcon'] || '/logo.png'), { - maxAge: app.enabled('cache') ? 5184000000 : 0 - }); - } -}; - -middleware.addExpiresHeaders = function(req, res, next) { - if (app.enabled('cache')) { - res.setHeader("Cache-Control", "public, max-age=5184000"); - res.setHeader("Expires", new Date(Date.now() + 5184000000).toUTCString()); - } else { - res.setHeader("Cache-Control", "public, max-age=0"); - res.setHeader("Expires", new Date().toUTCString()); - } - - next(); -}; - -middleware.privateTagListing = function(req, res, next) { - if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) { - controllers.helpers.notAllowed(req, res); - } else { - next(); - } -}; - -middleware.exposeGroupName = function(req, res, next) { - expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next); -}; - -middleware.exposeUid = function(req, res, next) { - expose('uid', user.getUidByUserslug, 'userslug', req, res, next); -}; - -function expose(exposedField, method, field, req, res, next) { - if (!req.params.hasOwnProperty(field)) { - return next(); - } - method(req.params[field], function(err, id) { - if (err) { - return next(err); - } - - res.locals[exposedField] = id; - next(); - }); -} - -middleware.requireUser = function(req, res, next) { - if (req.user) { - return next(); - } - - res.status(403).render('403', {title: '[[global:403.title]]'}); -}; - -middleware.privateUploads = function(req, res, next) { - if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) { - return next(); - } - if (req.path.startsWith('/uploads/files')) { - return res.status(403).json('not-allowed'); - } - next(); -}; - -middleware.busyCheck = function(req, res, next) { - if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) { - analytics.increment('errors:503'); - res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html')); - } else { - next(); - } -}; - -middleware.applyBlacklist = function(req, res, next) { - meta.blacklist.test(req.ip, function(err) { - next(err); - }); -}; - -middleware.processLanguages = function(req, res, next) { - var code = req.params.code; - var key = req.path.match(/[\w]+\.json/); - - if (code && key) { - languages.get(code, key[0], function(err, language) { - if (err) { - return next(err); - } - - res.status(200).json(language); - }); - } else { - res.status(404).json('{}'); - } -}; - -middleware.processTimeagoLocales = function(req, res, next) { - var fallback = req.path.indexOf('-short') === -1 ? 'jquery.timeago.en.js' : 'jquery.timeago.en-short.js', - localPath = path.join(__dirname, '../../public/vendor/jquery/timeago/locales', req.path), - exists; - - try { - exists = fs.accessSync(localPath, fs.F_OK | fs.R_OK); - } catch(e) { - exists = false; - } - - if (exists) { - res.status(200).sendFile(localPath, { - maxAge: app.enabled('cache') ? 5184000000 : 0 - }); - } else { - res.status(200).sendFile(path.join(__dirname, '../../public/vendor/jquery/timeago/locales', fallback), { - maxAge: app.enabled('cache') ? 5184000000 : 0 - }); - } -}; - -middleware.registrationComplete = function(req, res, next) { - // If the user's session contains registration data, redirect the user to complete registration - if (!req.session.hasOwnProperty('registration')) { - return next(); - } else { - if (!req.path.endsWith('/register/complete')) { - controllers.helpers.redirect(res, '/register/complete'); - } else { - return next(); - } - } -}; - -module.exports = function(webserver) { - app = webserver; - middleware.admin = require('./admin')(webserver); - - require('./header')(app, middleware); - require('./render')(middleware); - require('./maintenance')(middleware); - - return middleware; -}; diff --git a/src/middleware/user.js b/src/middleware/user.js new file mode 100644 index 0000000000..b70f7639d7 --- /dev/null +++ b/src/middleware/user.js @@ -0,0 +1,154 @@ +'use strict'; + +var async = require('async'); +var nconf = require('nconf'); +var meta = require('../meta'); +var user = require('../user'); + +var controllers = { + helpers: require('../controllers/helpers') +}; + +module.exports = function(middleware) { + + middleware.checkGlobalPrivacySettings = function(req, res, next) { + if (!req.user && !!parseInt(meta.config.privateUserInfo, 10)) { + return controllers.helpers.notAllowed(req, res); + } + + next(); + }; + + middleware.checkAccountPermissions = function(req, res, next) { + // This middleware ensures that only the requested user and admins can pass + async.waterfall([ + function (next) { + middleware.authenticate(req, res, next); + }, + function (next) { + user.getUidByUserslug(req.params.userslug, next); + }, + function (uid, next) { + if (parseInt(uid, 10) === req.uid) { + return next(null, true); + } + + user.isAdminOrGlobalMod(req.uid, next); + } + ], function (err, allowed) { + if (err || allowed) { + return next(err); + } + controllers.helpers.notAllowed(req, res); + }); + }; + + middleware.redirectToAccountIfLoggedIn = function(req, res, next) { + if (req.session.forceLogin) { + return next(); + } + + if (!req.user) { + return next(); + } + user.getUserField(req.user.uid, 'userslug', function (err, userslug) { + if (err) { + return next(err); + } + controllers.helpers.redirect(res, '/user/' + userslug); + }); + }; + + middleware.redirectUidToUserslug = function(req, res, next) { + var uid = parseInt(req.params.uid, 10); + if (!uid) { + return next(); + } + user.getUserField(uid, 'userslug', function(err, userslug) { + if (err || !userslug) { + return next(err); + } + + var path = req.path.replace(/^\/api/, '') + .replace('uid', 'user') + .replace(uid, function() { return userslug; }); + controllers.helpers.redirect(res, path); + }); + }; + + middleware.isAdmin = function(req, res, next) { + if (!req.uid) { + return controllers.helpers.notAllowed(req, res); + } + + user.isAdministrator(req.uid, function (err, isAdmin) { + if (err) { + return next(err); + } + + if (isAdmin) { + user.hasPassword(req.uid, function(err, hasPassword) { + if (err) { + return next(err); + } + + if (!hasPassword) { + return next(); + } + + var loginTime = req.session.meta ? req.session.meta.datetime : 0; + if (loginTime && parseInt(loginTime, 10) > Date.now() - 3600000) { + var timeLeft = parseInt(loginTime, 10) - (Date.now() - 3600000); + if (timeLeft < 300000) { + req.session.meta.datetime += 300000; + } + + return next(); + } + + req.session.returnTo = req.path.replace(/^\/api/, ''); + req.session.forceLogin = 1; + if (res.locals.isAPI) { + res.status(401).json({}); + } else { + res.redirect(nconf.get('relative_path') + '/login'); + } + }); + return; + } + + if (res.locals.isAPI) { + return controllers.helpers.notAllowed(req, res); + } + + middleware.buildHeader(req, res, function() { + controllers.helpers.notAllowed(req, res); + }); + }); + }; + + middleware.requireUser = function(req, res, next) { + if (req.user) { + return next(); + } + + res.status(403).render('403', {title: '[[global:403.title]]'}); + }; + + middleware.registrationComplete = function(req, res, next) { + // If the user's session contains registration data, redirect the user to complete registration + if (!req.session.hasOwnProperty('registration')) { + return next(); + } else { + if (!req.path.endsWith('/register/complete')) { + controllers.helpers.redirect(res, '/register/complete'); + } else { + return next(); + } + } + }; + +}; + + + diff --git a/src/plugins.js b/src/plugins.js index c531dfa27d..fc56d799c6 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -10,12 +10,10 @@ var nconf = require('nconf'); var db = require('./database'); var emitter = require('./emitter'); -var translator = require('../public/src/modules/translator'); var utils = require('../public/src/utils'); var hotswap = require('./hotswap'); var file = require('./file'); -var controllers = require('./controllers'); var app; var middleware; @@ -149,11 +147,13 @@ var middleware; Plugins.reloadRoutes = function(callback) { callback = callback || function() {}; var router = express.Router(); + router.hotswapId = 'plugins'; router.render = function() { app.render.apply(app, arguments); }; + var controllers = require('./controllers'); Plugins.fireHook('static:app.load', {app: app, router: router, middleware: middleware, controllers: controllers}, function(err) { if (err) { return winston.error('[plugins] Encountered error while executing post-router plugins hooks: ' + err.message); diff --git a/src/webserver.js b/src/webserver.js index 5ed7f44cd6..65b1eae0ff 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -1,25 +1,35 @@ 'use strict'; -var path = require('path'), - fs = require('fs'), - nconf = require('nconf'), - express = require('express'), - app = express(), - server, - winston = require('winston'), - async = require('async'), +var fs = require('fs'); +var path = require('path'); +var nconf = require('nconf'); +var express = require('express'); +var app = express(); +var server; +var winston = require('winston'); +var async = require('async'); +var flash = require('connect-flash'); +var compression = require('compression'); +var bodyParser = require('body-parser'); +var cookieParser = require('cookie-parser'); +var session = require('express-session'); +var useragent = require('express-useragent'); - emailer = require('./emailer'), - meta = require('./meta'), - languages = require('./languages'), - logger = require('./logger'), - plugins = require('./plugins'), - middleware = require('./middleware'), - routes = require('./routes'), - emitter = require('./emitter'), +var db = require('./database'); +var file = require('./file'); +var emailer = require('./emailer'); +var meta = require('./meta'); +var languages = require('./languages'); +var logger = require('./logger'); +var plugins = require('./plugins'); +var middleware = require('./middleware'); +var routes = require('./routes'); +var auth = require('./routes/authentication'); +var emitter = require('./emitter'); +var templates = require('templates.js'); - helpers = require('../public/src/modules/helpers'); +var helpers = require('../public/src/modules/helpers'); if (nconf.get('ssl')) { server = require('https').createServer({ @@ -46,7 +56,7 @@ server.on('error', function(err) { module.exports.listen = function() { emailer.registerApp(app); - app.locals.middleware = middleware = middleware(app); + setupExpressApp(app); helpers.register(); @@ -71,6 +81,69 @@ module.exports.listen = function() { }); }; +function setupExpressApp(app) { + var relativePath = nconf.get('relative_path'); + + app.engine('tpl', templates.__express); + app.set('view engine', 'tpl'); + app.set('views', nconf.get('views_dir')); + app.set('json spaces', process.env.NODE_ENV === 'development' ? 4 : 0); + app.use(flash()); + + app.enable('view cache'); + + app.use(compression()); + + setupFavicon(app); + + app.use(relativePath + '/apple-touch-icon', middleware.routeTouchIcon); + + app.use(bodyParser.urlencoded({extended: true})); + app.use(bodyParser.json()); + app.use(cookieParser()); + app.use(useragent.express()); + + var cookie = { + maxAge: 1000 * 60 * 60 * 24 * (parseInt(meta.config.loginDays, 10) || 14) + }; + + if (meta.config.cookieDomain) { + cookie.domain = meta.config.cookieDomain; + } + + if (nconf.get('secure')) { + cookie.secure = true; + } + + if (relativePath !== '') { + cookie.path = relativePath; + } + + app.use(session({ + store: db.sessionStore, + secret: nconf.get('secret'), + key: 'express.sid', + cookie: cookie, + resave: true, + saveUninitialized: true + })); + + app.use(middleware.addHeaders); + app.use(middleware.processRender); + auth.initialize(app, middleware); + + var toobusy = require('toobusy-js'); + toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100); + toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500); +} + +function setupFavicon(app) { + var faviconPath = path.join(__dirname, '../../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico'); + if (file.existsSync(faviconPath)) { + app.use(nconf.get('relative_path'), favicon(faviconPath)); + } +} + function initializeNodeBB(callback) { var skipJS, skipLess, fromFile = nconf.get('from-file') || ''; From a0f2e8a8ec82ebfbe2ad54c911b55ece7ff2e714 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 18:55:44 +0300 Subject: [PATCH 136/386] use req.uid :+1: --- src/middleware/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index 266f2b1488..7d5b5a4e80 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -53,7 +53,7 @@ middleware.pageView = function(req, res, next) { analytics.pageView({ ip: req.ip, path: req.path, - uid: req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') ? parseInt(req.user.uid, 10) : 0 + uid: req.uid }); plugins.fireHook('action:middleware.pageView', {req: req}); From df7cfe21469b0e1d6327d788103b34bd526e26e4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 19:12:55 +0300 Subject: [PATCH 137/386] missing favicon dep --- src/webserver.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/webserver.js b/src/webserver.js index 65b1eae0ff..ec80bf5f17 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -15,6 +15,7 @@ var bodyParser = require('body-parser'); var cookieParser = require('cookie-parser'); var session = require('express-session'); var useragent = require('express-useragent'); +var favicon = require('serve-favicon'); var db = require('./database'); var file = require('./file'); @@ -145,7 +146,7 @@ function setupFavicon(app) { } function initializeNodeBB(callback) { - var skipJS, skipLess, fromFile = nconf.get('from-file') || ''; + var skipJS, fromFile = nconf.get('from-file') || ''; if (fromFile.match('js')) { winston.info('[minifier] Minifying client-side JS skipped'); @@ -195,7 +196,7 @@ function cacheStaticFiles(callback) { callback(); } -function listen(callback) { +function listen() { var port = parseInt(nconf.get('port'), 10); if (Array.isArray(port)) { From 6d946425fa7649eb1726167d39ef848359492688 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 19:13:05 +0300 Subject: [PATCH 138/386] fix tests, circular deps --- src/categories.js | 4 +++- src/categories/create.js | 5 +++-- src/privileges/categories.js | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/categories.js b/src/categories.js index 539a643f80..46af3f1c5a 100644 --- a/src/categories.js +++ b/src/categories.js @@ -7,7 +7,7 @@ var db = require('./database'); var user = require('./user'); var Groups = require('./groups'); var plugins = require('./plugins'); -var privileges = require('./privileges'); + (function(Categories) { @@ -102,6 +102,7 @@ var privileges = require('./privileges'); }; Categories.getCategoriesByPrivilege = function(set, uid, privilege, callback) { + var privileges = require('./privileges'); async.waterfall([ function(next) { db.getSortedSetRange(set, 0, -1, next); @@ -238,6 +239,7 @@ var privileges = require('./privileges'); }; function getChildrenRecursive(category, uid, callback) { + var privileges = require('./privileges'); async.waterfall([ function (next) { db.getSortedSetRange('cid:' + category.cid + ':children', 0, -1, next); diff --git a/src/categories/create.js b/src/categories/create.js index e2d6960e45..09df61cb07 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -3,7 +3,7 @@ var async = require('async'); var db = require('../database'); -var privileges = require('../privileges'); + var groups = require('../groups'); var plugins = require('../plugins'); var utils = require('../../public/src/utils'); @@ -50,7 +50,7 @@ module.exports = function(Categories) { category = data.category; var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'posts:edit', 'posts:delete', 'topics:delete', 'upload:post:image']; - + var privileges = require('../privileges'); async.series([ async.apply(db.setObject, 'category:' + category.cid, category), function (next) { @@ -139,6 +139,7 @@ module.exports = function(Categories) { }; Categories.copyPrivilegesFrom = function(fromCid, toCid, callback) { + var privileges = require('../privileges'); async.each(privileges.privilegeList, function(privilege, next) { copyPrivilege(privilege, fromCid, toCid, next); }, callback); diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 0baf08af91..bebb50f4a9 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -5,7 +5,6 @@ var async = require('async'); var _ = require('underscore'); var user = require('../user'); -var categories = require('../categories'); var groups = require('../groups'); var helpers = require('./helpers'); var plugins = require('../plugins'); @@ -220,6 +219,7 @@ module.exports = function(privileges) { if (!cid) { return callback(null, false); } + var categories = require('../categories'); categories.getCategoryField(cid, 'disabled', function(err, disabled) { if (err) { return callback(err); @@ -269,6 +269,7 @@ module.exports = function(privileges) { }; privileges.categories.getBase = function(privilege, cids, uid, callback) { + var categories = require('../categories'); async.parallel({ categories: function(next) { categories.getCategoriesFields(cids, ['disabled'], next); From 56c4e13316cd8df80f314470a3018b176700b695 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 19:33:16 +0300 Subject: [PATCH 139/386] another dependency fix and test --- src/categories/delete.js | 2 +- src/categories/recentreplies.js | 6 +++--- src/categories/unread.js | 4 ++-- src/categories/update.js | 10 +++++----- tests/categories.js | 19 +++++++++++++++++++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/categories/delete.js b/src/categories/delete.js index 63f9fe324b..e7713bb426 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -5,7 +5,6 @@ var db = require('../database'); var batch = require('../batch'); var plugins = require('../plugins'); var topics = require('../topics'); -var privileges = require('../privileges'); var groups = require('../groups'); module.exports = function(Categories) { @@ -46,6 +45,7 @@ module.exports = function(Categories) { ], next); }, function(next) { + var privileges = require('../privileges'); async.each(privileges.privilegeList, function(privilege, next) { groups.destroy('cid:' + cid + ':privileges:' + privilege, next); }, next); diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 7a5e1cc1e8..bd89939078 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -10,7 +10,7 @@ var db = require('../database'); var posts = require('../posts'); var topics = require('../topics'); var categories = require('../categories'); -var privileges = require('../privileges'); + module.exports = function(Categories) { @@ -18,7 +18,7 @@ module.exports = function(Categories) { if (!parseInt(count, 10)) { return callback(null, []); } - + var privileges = require('../privileges'); async.waterfall([ function(next) { db.getSortedSetRevRange('cid:' + cid + ':pids', 0, count - 1, next); @@ -36,7 +36,7 @@ module.exports = function(Categories) { if (!Array.isArray(categoryData) || !categoryData.length) { return callback(); } - + var privileges = require('../privileges'); async.waterfall([ function(next) { async.map(categoryData, getRecentTopicTids, next); diff --git a/src/categories/unread.js b/src/categories/unread.js index d62aaa3c6e..37496e09b7 100644 --- a/src/categories/unread.js +++ b/src/categories/unread.js @@ -1,8 +1,8 @@ "use strict"; -var async = require('async'), - db = require('../database'); +var async = require('async'); +var db = require('../database'); module.exports = function(Categories) { diff --git a/src/categories/update.js b/src/categories/update.js index 78e97e4076..7c45663073 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -1,11 +1,11 @@ 'use strict'; -var async = require('async'), - db = require('../database'), - utils = require('../../public/src/utils'), - translator = require('../../public/src/modules/translator'), - plugins = require('../plugins'); +var async = require('async'); +var db = require('../database'); +var utils = require('../../public/src/utils'); +var translator = require('../../public/src/modules/translator'); +var plugins = require('../plugins'); module.exports = function(Categories) { diff --git a/tests/categories.js b/tests/categories.js index b607e6fc0a..9e762f5a90 100644 --- a/tests/categories.js +++ b/tests/categories.js @@ -54,6 +54,25 @@ describe('Categories', function() { }); }); + describe('Categories.getRecentTopicReplies', function() { + it('should not throw', function(done) { + Categories.getCategoryById({ + cid: categoryObj.cid, + set: 'cid:' + categoryObj.cid + ':tids', + reverse: true, + start: 0, + stop: -1, + uid: 0 + }, function(err, categoryData) { + assert.ifError(err); + Categories.getRecentTopicReplies(categoryData, 0, function(err) { + assert.ifError(err); + done(); + }); + }); + }); + }); + describe('.getCategoryTopics', function() { it('should return a list of topics', function(done) { Categories.getCategoryTopics({ From 89f550ce7cb4cbfecdf40d73f38c773310784ef7 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 26 Aug 2016 19:40:14 +0300 Subject: [PATCH 140/386] moved middleware requires --- src/categories/create.js | 1 - src/webserver.js | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/categories/create.js b/src/categories/create.js index 09df61cb07..fc841679d2 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -3,7 +3,6 @@ var async = require('async'); var db = require('../database'); - var groups = require('../groups'); var plugins = require('../plugins'); var utils = require('../../public/src/utils'); diff --git a/src/webserver.js b/src/webserver.js index ec80bf5f17..68ba499858 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -24,7 +24,6 @@ var meta = require('./meta'); var languages = require('./languages'); var logger = require('./logger'); var plugins = require('./plugins'); -var middleware = require('./middleware'); var routes = require('./routes'); var auth = require('./routes/authentication'); var emitter = require('./emitter'); @@ -83,6 +82,8 @@ module.exports.listen = function() { }; function setupExpressApp(app) { + var middleware = require('./middleware'); + var relativePath = nconf.get('relative_path'); app.engine('tpl', templates.__express); @@ -146,7 +147,9 @@ function setupFavicon(app) { } function initializeNodeBB(callback) { - var skipJS, fromFile = nconf.get('from-file') || ''; + var skipJS; + var fromFile = nconf.get('from-file') || ''; + var middleware = require('./middleware'); if (fromFile.match('js')) { winston.info('[minifier] Minifying client-side JS skipped'); From 785f4fc5a098ae01de48089bff56a6030b2df480 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 26 Aug 2016 15:36:42 -0400 Subject: [PATCH 141/386] updating to jQuery 3.x on frontend and ACP --- package.json | 1 + public/less/admin/admin.less | 6 + public/less/admin/manage/tags.less | 11 +- public/less/admin/manage/users.less | 9 + public/src/admin/manage/flags.js | 3 +- public/src/admin/manage/tags.js | 4 +- public/src/admin/manage/users.js | 14 +- public/src/admin/modules/selectable.js | 141 +- public/src/modules/translator.js | 4 +- public/vendor/bootstrap/js/bootstrap.js | 2377 ++++ public/vendor/bootstrap/js/bootstrap.min.js | 7 - .../smoothness/images/animated-overlay.gif | Bin 1738 -> 0 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 212 -> 0 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 208 -> 0 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 335 -> 335 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 207 -> 207 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 262 -> 262 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 262 -> 262 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 332 -> 332 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 280 -> 280 bytes .../images/ui-icons_222222_256x240.png | Bin 6922 -> 6922 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 4549 -> 4549 bytes .../images/ui-icons_454545_256x240.png | Bin 6992 -> 6992 bytes .../images/ui-icons_888888_256x240.png | Bin 6999 -> 6999 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 4549 -> 4549 bytes .../jquery-ui-1.10.4.custom.min.css | 7 - .../jquery/css/smoothness/jquery-ui.css | 845 ++ .../jquery/js/jquery-ui-1.10.4.custom.js | 7 - public/vendor/jquery/js/jquery-ui.js | 9857 +++++++++++++++++ src/meta/css.js | 7 +- src/meta/js.js | 5 +- src/views/admin/header.tpl | 4 - src/views/admin/manage/users.tpl | 4 +- 33 files changed, 13199 insertions(+), 114 deletions(-) create mode 100644 public/vendor/bootstrap/js/bootstrap.js delete mode 100644 public/vendor/bootstrap/js/bootstrap.min.js delete mode 100644 public/vendor/jquery/css/smoothness/images/animated-overlay.gif delete mode 100644 public/vendor/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100644 public/vendor/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png delete mode 100644 public/vendor/jquery/css/smoothness/jquery-ui-1.10.4.custom.min.css create mode 100644 public/vendor/jquery/css/smoothness/jquery-ui.css delete mode 100644 public/vendor/jquery/js/jquery-ui-1.10.4.custom.js create mode 100644 public/vendor/jquery/js/jquery-ui.js diff --git a/package.json b/package.json index 97c7ca45fa..cf80e99e15 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "html-to-text": "2.0.0", "ip": "1.1.2", "jimp": "0.2.21", + "jquery": "^3.1.0", "json-2-csv": "^2.0.22", "less": "^2.0.0", "logrotate-stream": "^0.2.3", diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index ed15e562e4..0ceeb050e5 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -265,4 +265,10 @@ body { [class^="col-"] .mdl-switch__label { padding-right: 15px; +} + +.ui-selectable-helper { + border: 1px dashed @brand-success; + background: lighten(@brand-success, 10%); + opacity: 0.5; } \ No newline at end of file diff --git a/public/less/admin/manage/tags.less b/public/less/admin/manage/tags.less index 6800778237..34075816b1 100644 --- a/public/less/admin/manage/tags.less +++ b/public/less/admin/manage/tags.less @@ -5,14 +5,23 @@ } .tag-row { - padding: 5px; + padding: 0.5rem; float: left; + margin-left: 0.5rem; .tag-item { cursor: pointer; display: inline-block; font-size: 11px; } + + &.ui-selected { + background: lighten(@brand-success, 25%); + } + + &.ui-selecting { + background: lighten(@brand-success, 40%); + } } } diff --git a/public/less/admin/manage/users.less b/public/less/admin/manage/users.less index bed33b77a1..0ab74c7544 100644 --- a/public/less/admin/manage/users.less +++ b/public/less/admin/manage/users.less @@ -16,6 +16,7 @@ height: auto; max-width: 145px; min-width: 145px; + padding: 1rem; img, .user-icon { .user-icon-style(80px, 4rem); @@ -44,5 +45,13 @@ } } } + + .ui-selected { + background: lighten(@brand-success, 25%); + } + + .ui-selecting { + background: lighten(@brand-success, 40%); + } } } \ No newline at end of file diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index 7a89c24845..a686c4a07e 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -3,10 +3,9 @@ define('admin/manage/flags', [ 'forum/infinitescroll', - 'admin/modules/selectable', 'autocomplete', 'Chart' -], function(infinitescroll, selectable, autocomplete, Chart) { +], function(infinitescroll, autocomplete, Chart) { var Flags = {}; diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index 9fda81eccb..0c0bc368c3 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -80,7 +80,7 @@ define('admin/manage/tags', [ function handleModify() { $('#modify').on('click', function() { - var tagsToModify = $('.tag-row.selected'); + var tagsToModify = $('.tag-row.ui-selected'); if (!tagsToModify.length) { return; } @@ -120,7 +120,7 @@ define('admin/manage/tags', [ function handleDeleteSelected() { $('#deleteSelected').on('click', function() { - var tagsToDelete = $('.tag-row.selected'); + var tagsToDelete = $('.tag-row.ui-selected'); if (!tagsToDelete.length) { return; } diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 1008928bc5..dfda934106 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -6,29 +6,29 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) var Users = {}; Users.init = function() { - selectable.enable('#users-container', '.user-selectable'); + selectable.enable('#users-container', '.users-box'); function getSelectedUids() { var uids = []; - $('#users-container .users-box .selected').each(function() { - uids.push($(this).parents('[data-uid]').attr('data-uid')); + $('#users-container .users-box.ui-selected').each(function() { + uids.push(this.getAttribute('data-uid')); }); return uids; } function update(className, state) { - $('#users-container .users-box .selected').siblings('.labels').find(className).each(function() { + $('#users-container .users-box.ui-selected .labels').find(className).each(function() { $(this).toggleClass('hide', !state); }); } function unselectAll() { - $('#users-container .users-box .selected').removeClass('selected'); + $('#users-container .users-box.ui-selected').removeClass('ui-selected'); } function removeSelected() { - $('#users-container .users-box .selected').parents('.users-box').remove(); + $('#users-container .users-box.ui-selected').remove(); } function done(successMessage, className, flag) { @@ -328,7 +328,7 @@ define('admin/manage/users', ['admin/modules/selectable'], function(selectable) .removeClass('label-danger'); } - selectable.enable('#users-container', '.user-selectable'); + selectable.enable('#users-container', '.users-box'); }); }); }, 250); diff --git a/public/src/admin/modules/selectable.js b/public/src/admin/modules/selectable.js index 81b4fcf06f..cce19bcdda 100644 --- a/public/src/admin/modules/selectable.js +++ b/public/src/admin/modules/selectable.js @@ -5,76 +5,81 @@ define('admin/modules/selectable', function() { var selectable = {}; - // modified from http://threedubmedia.com/code/event/drop/demo/selection - selectable.enable = function(parentElement, elementsToSelect, events) { - function selected(element) { - var $element = $(element).toggleClass('selected'); - - if (events && typeof events.onSelected === 'function') { - events.onSelected($element); - } - } - - function unselected(element) { - var $element = $(element).removeClass('selected'); - - if (events && typeof events.onUnselected === 'function') { - events.onUnselected($element); - } - } - - parentElement = $(parentElement); - elementsToSelect = $(elementsToSelect).not('.selection'); - - var offset = parentElement.offset(); - - parentElement - .addClass('selectable') - .on('mousedown', function(ev) { - if (!ev.shiftKey) { - unselected(elementsToSelect); - } - }) - .drag('start',function(ev, dd) { - if (!ev.shiftKey) { - unselected(elementsToSelect); - } - - return $('
    ') - .css('opacity', 0.65 ) - .appendTo(parentElement); - }) - .drag(function(ev, dd){ - $(dd.proxy).css({ - top: Math.min(ev.pageY - offset.top, dd.startY - offset.top), - left: Math.min(ev.pageX - offset.left, dd.startX - offset.left), - height: Math.abs(ev.pageY - dd.startY), - width: Math.abs(ev.pageX - dd.startX) - }); - }) - .drag('end',function(ev, dd){ - $(dd.proxy).remove(); - }); - - elementsToSelect - .addClass('selection') - .on('mouseup', function(ev) { - selected(this); - }) - .drop('start',function(){ - $(this).addClass('active'); - }) - .drop(function( ev, dd ){ - selected(this); - }) - .drop('end',function(){ - $(this).removeClass('active'); - }); - - $.drop({ - multi: true + selectable.enable = function(containerEl, targets) { + $(containerEl).selectable({ + filter: targets }); }; + // // modified from http://threedubmedia.com/code/event/drop/demo/selection + // selectable.enable = function(parentElement, elementsToSelect, events) { + // function selected(element) { + // var $element = $(element).toggleClass('selected'); + + // if (events && typeof events.onSelected === 'function') { + // events.onSelected($element); + // } + // } + + // function unselected(element) { + // var $element = $(element).removeClass('selected'); + + // if (events && typeof events.onUnselected === 'function') { + // events.onUnselected($element); + // } + // } + + // parentElement = $(parentElement); + // elementsToSelect = $(elementsToSelect).not('.selection'); + + // var offset = parentElement.offset(); + + // parentElement + // .addClass('selectable') + // .on('mousedown', function(ev) { + // if (!ev.shiftKey) { + // unselected(elementsToSelect); + // } + // }) + // .drag('start',function(ev, dd) { + // if (!ev.shiftKey) { + // unselected(elementsToSelect); + // } + + // return $('
    ') + // .css('opacity', 0.65 ) + // .appendTo(parentElement); + // }) + // .drag(function(ev, dd){ + // $(dd.proxy).css({ + // top: Math.min(ev.pageY - offset.top, dd.startY - offset.top), + // left: Math.min(ev.pageX - offset.left, dd.startX - offset.left), + // height: Math.abs(ev.pageY - dd.startY), + // width: Math.abs(ev.pageX - dd.startX) + // }); + // }) + // .drag('end',function(ev, dd){ + // $(dd.proxy).remove(); + // }); + + // elementsToSelect + // .addClass('selection') + // .on('mouseup', function(ev) { + // selected(this); + // }) + // .drop('start',function(){ + // $(this).addClass('active'); + // }) + // .drop(function( ev, dd ){ + // selected(this); + // }) + // .drop('end',function(){ + // $(this).removeClass('active'); + // }); + + // $.drop({ + // multi: true + // }); + // }; return selectable; }); diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index ad18721605..00a9126448 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -96,12 +96,12 @@ break; } - $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').success(function() { + $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(function() { $('.timeago').timeago(); translator.timeagoShort = $.extend({}, jQuery.timeago.settings.strings); // Retrieve the shorthand timeago values as well - $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').success(function() { + $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function() { // Switch back to long-form translator.toggleTimeagoShorthand(); }); diff --git a/public/vendor/bootstrap/js/bootstrap.js b/public/vendor/bootstrap/js/bootstrap.js new file mode 100644 index 0000000000..8a2e99a535 --- /dev/null +++ b/public/vendor/bootstrap/js/bootstrap.js @@ -0,0 +1,2377 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.7 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.7 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.7' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector === '#' ? [] : selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.7 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.7' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d).prop(d, true) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d).prop(d, false) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target).closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { + // Prevent double click on radios, and the double selections (so cancellation) on checkboxes + e.preventDefault() + // The target component still receive the focus + if ($btn.is('input,button')) $btn.trigger('focus') + else $btn.find('input:visible,button:visible').first().trigger('focus') + } + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.7 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.7' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.7 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + +/* jshint latedef: false */ + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.7' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.7 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.7' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger($.Event('shown.bs.dropdown', relatedTarget)) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.7 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.7' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (document !== e.target && + this.$element[0] !== e.target && + !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.7 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.7' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + } + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var isSvg = window.SVGElement && el instanceof window.SVGElement + // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. + // See https://github.com/twbs/bootstrap/issues/20280 + var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + that.$element = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.7 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.7' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.7 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.7' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.7 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.7' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.7 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.7' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/public/vendor/bootstrap/js/bootstrap.min.js b/public/vendor/bootstrap/js/bootstrap.min.js deleted file mode 100644 index e79c065134..0000000000 --- a/public/vendor/bootstrap/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.3.6 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under the MIT license - */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/public/vendor/jquery/css/smoothness/images/animated-overlay.gif b/public/vendor/jquery/css/smoothness/images/animated-overlay.gif deleted file mode 100644 index d441f75ebfbdf26a265dfccd670120d25c0a341c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1738 zcmZ|OX;ji_6b5ixNYt8>l?gOuO)6lU%W(mxn(`>1S(XO;u`D+P%xqBvMr|w-Vyr1s z7R|Cn0b8|Hu<=Zmv1mFqh9Fj!NuZfKB2MP$e75`XJ@>=!y!Ux9xR3x;EW!q1^V>X| znVFuRUN`NqJ2)ybXh%e__h!!pv(M|S3+?9F%(K}zyE40MGyhWF5-IDgL&=%2-9`Nk z!1@8uk4t%_{(K~>N;sK&dzJbwJ=$kYTlL=$%#0Pfh>U{%i@~wWbvYsD_K-D`&+u1( z#Ma`>%q<^UhzGvi(hyE`zCD{-=2|zL5>wnB=DE!U?(CZG%q4@lDnCq_%&3DCla#(X zmBhDD+RN$aMWWHm?ig*>1Onn6~r?Ma~N2JKAxN>H%UtRyRqS)6Um!-Tz%-r=& zQmTb^JFIe3W^-kAm`}`2P|niMh>RYyd)S^f(dbrx965?rzbhP|XeP}o&&DSZ4|oYQ z)I{f!SfycYw?3=9W;o-B%U5xs(pP267X~9-7L|4WzaYexC0GtG8wWygm63rF{llCEraxzkc=IxvFQ-y37=_;e5 zJLq^gsSO0Ayz?a>E_?{dmUc+t#qv$)XN8$<<}rQ#)lsiw+pmL&J>~+hgpo>i$m+;l zZIa_ZRIfSeT$~v5d`EBV&*k`apPgjv&B|+d`Q!nyu{L4rs%ZfoF0*Kq8I%ByOcFpL zK=>wzofZo<+0GZLCnWM3oQ^pb(gRSf02;~cEn@LJ>~XB9IkEX{$N#Z`m%>S!U{uPx zloI%bLdo$Adxlh(Uv^yX7s5G&C zLwNRG>~T?G{kzupp8EcyLGPoPf)@&9Wqfw_l&uU-6cexk%5;uQg%wb=0k_733{i#& z1a2p)gV3S2+QG1-K9tZ}E~I<(P0r2aFFY-c{o?TUOz3Xjod#TLE2A_c?*T7t z=1>~%YW450{Qqno4t`}gvLnuMrcu8+#xEBoY%2_+Mb#Z6S38+r*M4O`-+!zl(@m`D zQsi|GA2l3gEy}LFe<#Hv8?$_L#u8E|3-bP$*La*E>B{X!Sy4i6?TKam!49aXCAW4S*P_O^H4^*DpiA40o}Uqw~Eo&veh1`|8i zD2$x+>_b^bXE4N;AW=5>iYak2%!JAh0j1*k1{p#iRCjbB7!cSws~U{1IA@acLII$t z$>X#A+^s6iJ5~DFG!xa?>z{=lxtdi1rzbM-(nqAu3D8h-&64xo6|E!p?pK0xT;qoK z`6%+SpBk+~M?nO}>2mTw!A{yZ6O>Z@kwSd4;8aWU5z!P~tQl?u==^+R`{OmOS}oZh zOXQ3{6kuz?Is^n^L7;9ieB9C+8B{>t+pDrlq4xGDDn#T#3T5$l1g`FTQkU;b-981j zNm{zC`$wn7etklM#qHI4=3m5gwa6DNS{?Z!vSObi_od{4eUo=_S2BKNpkSdiqe(k9WtkeM79;2-%CFbb)aB=&H1?i1}uwFzoZQ(38Kn1zBP ORn*B%u*Wk|4g3!*Rv{Mv diff --git a/public/vendor/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/public/vendor/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100644 index 42319a6877d6e3741a652c00e52f31cf9ffd219c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F1SA+{?>A)!QcOwS?k)_>#w|r1Kptm-M`SUO z_5fqIli7AahM1>|V~EA+ zRdP`(kYX@0Ff`FMFwr$O2r)FYGBLF>G|)9Lw=yu`O+UB{MMG|WN@iLmZVfe7l~O?)F zK#IZ0z|ch3z(m*BAjHtn%EZLV)LhrV+{(aUvB#`8C>nC}Q!>*kacj6FsuTd!z~JfX K=d#Wzp$P!<&on>) diff --git a/public/vendor/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/public/vendor/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png index 9ed29e7f7f2fed091802622c34d00a5139f25a77..3e87efd7e9c8ea4d5475753108b379216a061010 100644 GIT binary patch delta 94 zcmX@lbe?I#k8m?x0}EXvvk*fID-$CtBV%m?11kfAw#?kM3=9maC9V-ADTyViR=N2p dnQ4^_Mh1r1HGDU@DhSlT;OXk;vd$@?2>=EC8Z7_- delta 94 zcmX@lbe?I#k8l%R0~1|igAhYQD-%;I0}EXPb1MUbPfGpq3=9maC9V-ADTyViR=N2p dnQ4^_Mh1r1H8j_oKL%=G@O1TaS?83{1ONfa8Y=(* diff --git a/public/vendor/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/public/vendor/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png index f0e86f1a765581a7ed4b724c79e8503e462654cc..e1602e3b8b7cf81a60c312e85baabb3ec16df03b 100644 GIT binary patch delta 94 zcmX@lc%E@WMYx%+frYM-S%{&9m5Gs+k&(86ft7)Qf?S#;0|SFRc?Mt dW?ChKk%1w04S~-0mI5^}c)I$ztaD0e0sw@g7-Ikc delta 94 zcmX@lc%E@WMYxHsfr+lML5QKDm5GU!sj;qsxs`!IIQzc;3=9maC9V-ADTyViR=N2p dnQ4^_Mh1r1HK;|gIRiB?c)I$ztaD0e0szI77;OLm diff --git a/public/vendor/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/public/vendor/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png index 15d191d79b0ddce6750b6e97f662d7778cc419f3..278b8b23fe119c97b828f220ed3720540fe4d559 100644 GIT binary patch delta 94 zcmZo;YGazPKHN;#z(Uu^EX2^l%EZXZ$XMIJz{Rc?Mt dW?ChKk%1w04c|?!3Ia7Sc)I$ztaD0e0syyz8BzcM delta 94 zcmZo;YGazPKHNmtz(m*BAjHtn%EZ(P$Tl#yGB7Z0daBL9z@S><8c~vxSdwa$o1c=I bR>@#wV2E9V%l+8tKn)C@u6{1-oD!M_fq_A_#5JNMC9x#cDmOnR dGp&-r$iNW0hCt_gOMw~~JYD@<);T3K0RUrg7zzLY delta 94 zcmZo;YGazPKHNmtz(m*BAjHtn%EZLV)LhrV+{(aUvB#`83=9maC9V-ADTyViR=N2p dnQ4^_Mh1r1HCz%^3IJ+g@O1TaS?83{1OUX@83F(R diff --git a/public/vendor/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png b/public/vendor/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png index 31b9eb03dd4f6daeb4f1c4628be656a62d2b3f20..443633de42549092602a51a9a47fff90838ee297 100644 GIT binary patch delta 76 zcmX@ZbcSidS1B`H0}EXvvk*fID-$CtBV%m?11kfAw#?kMlld9N)v!x{H@Pav00f?{ KelF{r5}E)VToft* delta 76 zcmX@ZbcSidS1A)+0~1|igAhYQD-%;I17lqSb1MUb530A9PUdG6SHmvdSdlEr00f?{ KelF{r5}E)hh!hF{ diff --git a/public/vendor/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/public/vendor/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png index 75831b53483643a7e6e595b4b259fdbd6efa045a..cd72b628dcfb922f1c08587b0389224783f19485 100644 GIT binary patch delta 94 zcmbQiG=piv!EiHO0}EXvvk*fID-$CtBV%m?11kfAw#?kM3=9maC9V-ADTyViR=N2p dnQ4^_Mh1r1HGDU@DhSlT;OXk;vd$@?2>{C38HfM? delta 94 zcmbQiG=piv!Eh5@0~1|igAhYQD-#ndQwv=Kb1MUbP=;5m3=9maC9V-ADTyViR=N2p dnQ4^_Mh1r1HK@b}?F4FI@O1TaS?83{1OSiA7(f63 diff --git a/public/vendor/jquery/css/smoothness/images/ui-icons_222222_256x240.png b/public/vendor/jquery/css/smoothness/images/ui-icons_222222_256x240.png index c1cb1170c8b3795835b8831ab81fa9ae63b606b1..e723e17cb5428094de75a0d3c6f4f3c07dbd3bbe 100644 GIT binary patch delta 87 zcmeA&>oVKWC*^IXYhbQxXdGf_U}a=zWooHyU|?lnAkEm>&A`B*TH+c}l9E`GYL%Ox gl9^V?U}Rtj(_mm}Wn^Y$Y68*l`RoFL$v3480s1)@6aWAK delta 87 zcmeA&>oVKWC*^IdYha{nU>IUxYGrI}WoV#lU}0ropqX%B5(5K+YKdz^NlIc#s#R`& XN@iLmgOPzDb`24>6?&6zN*MwG5_A`I diff --git a/public/vendor/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png b/public/vendor/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png index 84b601bf0f726bf95801da487deaf2344a32e4b8..1f5f49756ca64bd20a1048bd7a3a584457d4bf00 100644 GIT binary patch delta 87 zcmX@Ad{lXZr=Yi)u7SC(p>c?zft8V=m8qq+fq|8Qfiz=hHvCa~u2uzljHUt2%ITI@Y delta 69 zcmca$cEN1J6)7oWT>~Rs1H%vlQ!8U*D?g3lP15D62~DOVOya$Szg)@ E0KMH3ivR!s diff --git a/public/vendor/jquery/css/smoothness/images/ui-icons_888888_256x240.png b/public/vendor/jquery/css/smoothness/images/ui-icons_888888_256x240.png index feea0e20264c4649b2ef03fe6705d69b4937c04e..ee5e33f27235d1b5cf0259f63f3894a6be33c679 100644 GIT binary patch delta 87 zcmca^cHL~lZ7FXvT?2DnL*o!b11lp#D^p8t0|P4q18K(2ZUzPh)e_f;l9a@fRIA+l hl+3hB1|tJQm<9t&D~Rs1H%vlQ!8U*D?g3lNcBnR7+eVN>UO_Qmu0H XQ!>*k8H@}Jv1^F1tc?zft8V=m8qq+fq|8Qfiz=hHv=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("
    "),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widths?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(t){t.widget("ui.draggable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"!==this.options.helper||/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(t(i.iframeFix===!0?"iframe":i.iframeFix).each(function(){t("
    ").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(t(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offsetParent=this.helper.offsetParent(),this.offsetParentCssPosition=this.offsetParent.css("position"),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.offset.scroll=!1,t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_mouseDrag:function(e,i){if("fixed"===this.offsetParentCssPosition&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"original"!==this.options.helper||t.contains(this.element[0].ownerDocument,this.element[0])?("invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1):!1},_mouseUp:function(e){return t("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return s.parents("body").length||s.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s[0]===this.element[0]||/(fixed|absolute)/.test(s.css("position"))||s.css("position","absolute"),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.element.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;return n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):"document"===n.containment?(this.containment=[0,0,t(document).width()-this.helperProportions.width-this.margins.left,(t(document).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],undefined):n.containment.constructor===Array?(this.containment=n.containment,undefined):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e="hidden"!==i.css("overflow"),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=i),undefined):(this.containment=null,undefined)},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent;return this.offset.scroll||(this.offset.scroll={top:n.scrollTop(),left:n.scrollLeft()}),{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top)*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)*s}},_generatePosition:function(e){var i,s,n,a,o=this.options,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=e.pageX,h=e.pageY;return this.offset.scroll||(this.offset.scroll={top:r.scrollTop(),left:r.scrollLeft()}),this.originalPosition&&(this.containment&&(this.relative_container?(s=this.relative_container.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.lefti[2]&&(l=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(h=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,h=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((l-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,l=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a)),{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():this.offset.scroll.top),left:l-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s]),"drag"===e&&(this.positionAbs=this._convertPositionTo("absolute")),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i){var s=t(this).data("ui-draggable"),n=s.options,a=t.extend({},i,{item:s.element});s.sortables=[],t(n.connectToSortable).each(function(){var i=t.data(this,"ui-sortable");i&&!i.options.disabled&&(s.sortables.push({instance:i,shouldRevert:i.options.revert}),i.refreshPositions(),i._trigger("activate",e,a))})},stop:function(e,i){var s=t(this).data("ui-draggable"),n=t.extend({},i,{item:s.element});t.each(s.sortables,function(){this.instance.isOver?(this.instance.isOver=0,s.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=this.shouldRevert),this.instance._mouseStop(e),this.instance.options.helper=this.instance.options._helper,"original"===s.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",e,n))})},drag:function(e,i){var s=t(this).data("ui-draggable"),n=this;t.each(s.sortables,function(){var a=!1,o=this;this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(a=!0,t.each(s.sortables,function(){return this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this!==o&&this.instance._intersectsWith(this.instance.containerCache)&&t.contains(o.instance.element[0],this.instance.element[0])&&(a=!1),a})),a?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=t(n).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return i.helper[0]},e.target=this.instance.currentItem[0],this.instance._mouseCapture(e,!0),this.instance._mouseStart(e,!0,!0),this.instance.offset.click.top=s.offset.click.top,this.instance.offset.click.left=s.offset.click.left,this.instance.offset.parent.left-=s.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=s.offset.parent.top-this.instance.offset.parent.top,s._trigger("toSortable",e),s.dropped=this.instance.element,s.currentItem=s.element,this.instance.fromOutside=s),this.instance.currentItem&&this.instance._mouseDrag(e)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",e,this.instance._uiHash(this.instance)),this.instance._mouseStop(e,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),s._trigger("fromSortable",e),s.dropped=!1)})}}),t.ui.plugin.add("draggable","cursor",{start:function(){var e=t("body"),i=t(this).data("ui-draggable").options;e.css("cursor")&&(i._cursor=e.css("cursor")),e.css("cursor",i.cursor)},stop:function(){var e=t(this).data("ui-draggable").options;e._cursor&&t("body").css("cursor",e._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("opacity")&&(n._opacity=s.css("opacity")),s.css("opacity",n.opacity)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._opacity&&t(i.helper).css("opacity",s._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(){var e=t(this).data("ui-draggable");e.scrollParent[0]!==document&&"HTML"!==e.scrollParent[0].tagName&&(e.overflowOffset=e.scrollParent.offset())},drag:function(e){var i=t(this).data("ui-draggable"),s=i.options,n=!1;i.scrollParent[0]!==document&&"HTML"!==i.scrollParent[0].tagName?(s.axis&&"x"===s.axis||(i.overflowOffset.top+i.scrollParent[0].offsetHeight-e.pageY=0;u--)r=p.snapElements[u].left,l=r+p.snapElements[u].width,h=p.snapElements[u].top,c=h+p.snapElements[u].height,r-f>_||m>l+f||h-f>b||v>c+f||!t.contains(p.snapElements[u].item.ownerDocument,p.snapElements[u].item)?(p.snapElements[u].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=!1):("inner"!==g.snapMode&&(s=f>=Math.abs(h-b),n=f>=Math.abs(c-v),a=f>=Math.abs(r-_),o=f>=Math.abs(l-m),s&&(i.position.top=p._convertPositionTo("relative",{top:h-p.helperProportions.height,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r-p.helperProportions.width}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l}).left-p.margins.left)),d=s||n||a||o,"outer"!==g.snapMode&&(s=f>=Math.abs(h-v),n=f>=Math.abs(c-b),a=f>=Math.abs(r-m),o=f>=Math.abs(l-_),s&&(i.position.top=p._convertPositionTo("relative",{top:h,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c-p.helperProportions.height,left:0}).top-p.margins.top),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r}).left-p.margins.left),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:l-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[u].snapping&&(s||n||a||o||d)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=s||n||a||o||d)}}),t.ui.plugin.add("draggable","stack",{start:function(){var e,i=this.data("ui-draggable").options,s=t.makeArray(t(i.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});s.length&&(e=parseInt(t(s[0]).css("zIndex"),10)||0,t(s).each(function(i){t(this).css("zIndex",e+i)}),this.css("zIndex",e+s.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("zIndex")&&(n._zIndex=s.css("zIndex")),s.css("zIndex",n.zIndex)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._zIndex&&t(i.helper).css("zIndex",s._zIndex)}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}t.widget("ui.droppable",{version:"1.10.4",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],undefined):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},t.ui.ddmanager.droppables[i.scope]=t.ui.ddmanager.droppables[i.scope]||[],t.ui.ddmanager.droppables[i.scope].push(this),i.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){for(var e=0,i=t.ui.ddmanager.droppables[this.options.scope];i.length>e;e++)i[e]===this&&i.splice(e,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(e,i){"accept"===e&&(this.accept=t.isFunction(i)?i:function(t){return t.is(i)}),t.Widget.prototype._setOption.apply(this,arguments)},_activate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var e=t.data(this,"ui-droppable");return e.options.greedy&&!e.options.disabled&&e.options.scope===s.options.scope&&e.accept.call(e.element[0],s.currentItem||s.element)&&t.ui.intersect(s,t.extend(e,{offset:e.element.offset()}),e.options.tolerance)?(n=!0,!1):undefined}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}}}),t.ui.intersect=function(t,i,s){if(!i.offset)return!1;var n,a,o=(t.positionAbs||t.position.absolute).left,r=(t.positionAbs||t.position.absolute).top,l=o+t.helperProportions.width,h=r+t.helperProportions.height,c=i.offset.left,u=i.offset.top,d=c+i.proportions().width,p=u+i.proportions().height;switch(s){case"fit":return o>=c&&d>=l&&r>=u&&p>=h;case"intersect":return o+t.helperProportions.width/2>c&&d>l-t.helperProportions.width/2&&r+t.helperProportions.height/2>u&&p>h-t.helperProportions.height/2;case"pointer":return n=(t.positionAbs||t.position.absolute).left+(t.clickOffset||t.offset.click).left,a=(t.positionAbs||t.position.absolute).top+(t.clickOffset||t.offset.click).top,e(a,u,i.proportions().height)&&e(n,c,i.proportions().width);case"touch":return(r>=u&&p>=r||h>=u&&p>=h||u>r&&h>p)&&(o>=c&&d>=o||l>=c&&d>=l||c>o&&l>d);default:return!1}},t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,a=t.ui.ddmanager.droppables[e.options.scope]||[],o=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||e&&!a[s].accept.call(a[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue t}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&t.ui.intersect(e,this,this.options.tolerance)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").bind("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=t.ui.intersect(e,this,this.options.tolerance),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return t.data(this,"ui-droppable").options.scope===n}),a.length&&(s=t.data(a[0],"ui-droppable"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").unbind("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}}})(jQuery);(function(t){function e(t){return parseInt(t,10)||0}function i(t){return!isNaN(parseInt(t,10))}t.widget("ui.resizable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var e,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(t("
    ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),e=this.handles.split(","),this.handles={},i=0;e.length>i;i++)s=t.trim(e[i]),a="ui-resizable-"+s,n=t("
    "),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(e){var i,s,n,a;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=t(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=t(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,a),this._proportionallyResize()),t(this.handles[i]).length},this._renderAxis(this.element),this._handles=t(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),t(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(t(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(t(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(i){var s,n,a,o=this.options,r=this.element.position(),h=this.element;return this.resizing=!0,/absolute/.test(h.css("position"))?h.css({position:"absolute",top:h.css("top"),left:h.css("left")}):h.is(".ui-draggable")&&h.css({position:"absolute",top:r.top,left:r.left}),this._renderProxy(),s=e(this.helper.css("left")),n=e(this.helper.css("top")),o.containment&&(s+=t(o.containment).scrollLeft()||0,n+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:s,top:n},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:h.width(),height:h.height()},this.originalSize=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalPosition={left:s,top:n},this.sizeDiff={width:h.outerWidth()-h.width(),height:h.outerHeight()-h.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,a=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===a?this.axis+"-resize":a),h.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(e){var i,s=this.helper,n={},a=this.originalMousePosition,o=this.axis,r=this.position.top,h=this.position.left,l=this.size.width,c=this.size.height,u=e.pageX-a.left||0,d=e.pageY-a.top||0,p=this._change[o];return p?(i=p.apply(this,[e,u,d]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),this.position.top!==r&&(n.top=this.position.top+"px"),this.position.left!==h&&(n.left=this.position.left+"px"),this.size.width!==l&&(n.width=this.size.width+"px"),this.size.height!==c&&(n.height=this.size.height+"px"),s.css(n),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(n)||this._trigger("resize",e,this.ui()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&t.ui.hasScroll(i[0],"left")?0:c.sizeDiff.height,a=s?0:c.sizeDiff.width,o={width:c.helper.width()-a,height:c.helper.height()-n},r=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null,h=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(o,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(t){var e,s,n,a,o,r=this.options;o={minWidth:i(r.minWidth)?r.minWidth:0,maxWidth:i(r.maxWidth)?r.maxWidth:1/0,minHeight:i(r.minHeight)?r.minHeight:0,maxHeight:i(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,n=o.minWidth/this.aspectRatio,s=o.maxHeight*this.aspectRatio,a=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),n>o.minHeight&&(o.minHeight=n),o.maxWidth>s&&(o.maxWidth=s),o.maxHeight>a&&(o.maxHeight=a)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),i(t.left)&&(this.position.left=t.left),i(t.top)&&(this.position.top=t.top),i(t.height)&&(this.size.height=t.height),i(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,s=this.size,n=this.axis;return i(t.height)?t.width=t.height*this.aspectRatio:i(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===n&&(t.left=e.left+(s.width-t.width),t.top=null),"nw"===n&&(t.top=e.top+(s.height-t.height),t.left=e.left+(s.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,s=this.axis,n=i(t.width)&&e.maxWidth&&e.maxWidtht.width,r=i(t.height)&&e.minHeight&&e.minHeight>t.height,h=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,c=/sw|nw|w/.test(s),u=/nw|ne|n/.test(s);return o&&(t.width=e.minWidth),r&&(t.height=e.minHeight),n&&(t.width=e.maxWidth),a&&(t.height=e.maxHeight),o&&c&&(t.left=h-e.minWidth),n&&c&&(t.left=h-e.maxWidth),r&&u&&(t.top=l-e.minHeight),a&&u&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var t,e,i,s,n,a=this.helper||this.element;for(t=0;this._proportionallyResizeElements.length>t;t++){if(n=this._proportionallyResizeElements[t],!this.borderDif)for(this.borderDif=[],i=[n.css("borderTopWidth"),n.css("borderRightWidth"),n.css("borderBottomWidth"),n.css("borderLeftWidth")],s=[n.css("paddingTop"),n.css("paddingRight"),n.css("paddingBottom"),n.css("paddingLeft")],e=0;i.length>e;e++)this.borderDif[e]=(parseInt(i[e],10)||0)+(parseInt(s[e],10)||0);n.css({height:a.height()-this.borderDif[0]-this.borderDif[2]||0,width:a.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("
    "),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).data("ui-resizable"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&t.ui.hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,c=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var i,s,n,a,o,r,h,l=t(this).data("ui-resizable"),c=l.options,u=l.element,d=c.containment,p=d instanceof t?d.get(0):/parent/.test(d)?u.parent().get(0):d;p&&(l.containerElement=t(p),/document/.test(d)||d===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(i=t(p),s=[],t(["Top","Right","Left","Bottom"]).each(function(t,n){s[t]=e(i.css("padding"+n))}),l.containerOffset=i.offset(),l.containerPosition=i.position(),l.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},n=l.containerOffset,a=l.containerSize.height,o=l.containerSize.width,r=t.ui.hasScroll(p,"left")?p.scrollWidth:o,h=t.ui.hasScroll(p)?p.scrollHeight:a,l.parentData={element:p,left:n.left,top:n.top,width:r,height:h}))},resize:function(e){var i,s,n,a,o=t(this).data("ui-resizable"),r=o.options,h=o.containerOffset,l=o.position,c=o._aspectRatio||e.shiftKey,u={top:0,left:0},d=o.containerElement;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-u.left),c&&(o.size.height=o.size.width/o.aspectRatio),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),c&&(o.size.width=o.size.height*o.aspectRatio),o.position.top=o._helper?h.top:0),o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top,i=Math.abs((o._helper?o.offset.left-u.left:o.offset.left-u.left)+o.sizeDiff.width),s=Math.abs((o._helper?o.offset.top-u.top:o.offset.top-h.top)+o.sizeDiff.height),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a&&(i-=Math.abs(o.parentData.left)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,c&&(o.size.height=o.size.width/o.aspectRatio)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,c&&(o.size.width=o.size.height*o.aspectRatio))},stop:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.containerOffset,n=e.containerPosition,a=e.containerElement,o=t(e.helper),r=o.offset(),h=o.outerWidth()-e.sizeDiff.width,l=o.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(a.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(a.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=function(e){t(e).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseInt(e.width(),10),height:parseInt(e.height(),10),left:parseInt(e.css("left"),10),top:parseInt(e.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):t.each(i.alsoResize,function(t){s(t)})},resize:function(e,i){var s=t(this).data("ui-resizable"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0},h=function(e,s){t(e).each(function(){var e=t(this),n=t(this).data("ui-resizable-alsoresize"),a={},o=s&&s.length?s:e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(n[e]||0)+(r[e]||0);i&&i>=0&&(a[e]=i||null)}),e.css(a)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):t.each(n.alsoResize,function(t,e){h(t,e)})},stop:function(){t(this).removeData("resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).data("ui-resizable");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).data("ui-resizable");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size,n=e.originalSize,a=e.originalPosition,o=e.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,h=r[0]||1,l=r[1]||1,c=Math.round((s.width-n.width)/h)*h,u=Math.round((s.height-n.height)/l)*l,d=n.width+c,p=n.height+u,f=i.maxWidth&&d>i.maxWidth,g=i.maxHeight&&p>i.maxHeight,m=i.minWidth&&i.minWidth>d,v=i.minHeight&&i.minHeight>p;i.grid=r,m&&(d+=h),v&&(p+=l),f&&(d-=h),g&&(p-=l),/^(se|s|e)$/.test(o)?(e.size.width=d,e.size.height=p):/^(ne)$/.test(o)?(e.size.width=d,e.size.height=p,e.position.top=a.top-u):/^(sw)$/.test(o)?(e.size.width=d,e.size.height=p,e.position.left=a.left-c):(p-l>0?(e.size.height=p,e.position.top=a.top-u):(e.size.height=l,e.position.top=a.top+n.height-l),d-h>0?(e.size.width=d,e.position.left=a.left-c):(e.size.width=h,e.position.left=a.left+n.width-h))}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))}t.widget("ui.sortable",t.ui.mouse,{version:"1.10.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_setOption:function(e,i){"disabled"===e?(this.options[e]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+l>r&&h>s+l,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i="x"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s="y"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&"right"===a||"down"===o?2:1:o&&("down"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&s||"left"===o&&!s:n&&("down"===n&&i||"up"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],h=[],l=this._connectWith();if(l&&e)for(s=l.length-1;s>=0;s--)for(o=t(l[s]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&h.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(h.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]).addClass(i||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?e.currentItem.children().each(function(){t(" ",e.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(n)}):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?"up":"down"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger("change",s,this._uiHash()),this.containers[g]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.leftthis.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger("beforeStop",t,this._uiHash()),s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery);(function(e){e.widget("ui.autocomplete",{version:"1.10.4",defaultElement:"",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("
      ").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().data("ui-menu"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){if(this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type)))return this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),undefined;var s=i.item.data("ui-autocomplete-item");!1!==this._trigger("focus",t,{item:s})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(s.value):this.liveRegion.text(s.value)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertBefore(this.element),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length").append(e("").text(i.label)).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this._value(this.term),this.menu.blur(),undefined):(this.menu[e](t),undefined):(this.search(null,t),undefined)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var t;this._superApply(arguments),this.options.disabled||this.cancelSearch||(t=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.text(t))}})})(jQuery);(function(e,t){function i(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},e.extend(this._defaults,this.regional[""]),this.dpDiv=a(e("
      "))}function a(t){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return t.delegate(i,"mouseout",function(){e(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).removeClass("ui-datepicker-next-hover")}).delegate(i,"mouseover",function(){e.datepicker._isDisabledDatepicker(n.inline?t.parent()[0]:n.input[0])||(e(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),e(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).addClass("ui-datepicker-next-hover"))})}function s(t,i){e.extend(t,i);for(var a in i)null==i[a]&&(t[a]=i[a]);return t}e.extend(e.ui,{datepicker:{version:"1.10.4"}});var n,r="datepicker";e.extend(i.prototype,{markerClassName:"hasDatepicker",maxRows:4,_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(e){return s(this._defaults,e||{}),this},_attachDatepicker:function(t,i){var a,s,n;a=t.nodeName.toLowerCase(),s="div"===a||"span"===a,t.id||(this.uuid+=1,t.id="dp"+this.uuid),n=this._newInst(e(t),s),n.settings=e.extend({},i||{}),"input"===a?this._connectDatepicker(t,n):s&&this._inlineDatepicker(t,n)},_newInst:function(t,i){var s=t[0].id.replace(/([^A-Za-z0-9_\-])/g,"\\\\$1");return{id:s,input:t,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:i,dpDiv:i?a(e("
      ")):this.dpDiv}},_connectDatepicker:function(t,i){var a=e(t);i.append=e([]),i.trigger=e([]),a.hasClass(this.markerClassName)||(this._attachments(a,i),a.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp),this._autoSize(i),e.data(t,r,i),i.settings.disabled&&this._disableDatepicker(t))},_attachments:function(t,i){var a,s,n,r=this._get(i,"appendText"),o=this._get(i,"isRTL");i.append&&i.append.remove(),r&&(i.append=e(""+r+""),t[o?"before":"after"](i.append)),t.unbind("focus",this._showDatepicker),i.trigger&&i.trigger.remove(),a=this._get(i,"showOn"),("focus"===a||"both"===a)&&t.focus(this._showDatepicker),("button"===a||"both"===a)&&(s=this._get(i,"buttonText"),n=this._get(i,"buttonImage"),i.trigger=e(this._get(i,"buttonImageOnly")?e("").addClass(this._triggerClass).attr({src:n,alt:s,title:s}):e("").addClass(this._triggerClass).html(n?e("").attr({src:n,alt:s,title:s}):s)),t[o?"before":"after"](i.trigger),i.trigger.click(function(){return e.datepicker._datepickerShowing&&e.datepicker._lastInput===t[0]?e.datepicker._hideDatepicker():e.datepicker._datepickerShowing&&e.datepicker._lastInput!==t[0]?(e.datepicker._hideDatepicker(),e.datepicker._showDatepicker(t[0])):e.datepicker._showDatepicker(t[0]),!1}))},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t,i,a,s,n=new Date(2009,11,20),r=this._get(e,"dateFormat");r.match(/[DM]/)&&(t=function(e){for(i=0,a=0,s=0;e.length>s;s++)e[s].length>i&&(i=e[s].length,a=s);return a},n.setMonth(t(this._get(e,r.match(/MM/)?"monthNames":"monthNamesShort"))),n.setDate(t(this._get(e,r.match(/DD/)?"dayNames":"dayNamesShort"))+20-n.getDay())),e.input.attr("size",this._formatDate(e,n).length)}},_inlineDatepicker:function(t,i){var a=e(t);a.hasClass(this.markerClassName)||(a.addClass(this.markerClassName).append(i.dpDiv),e.data(t,r,i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(t),i.dpDiv.css("display","block"))},_dialogDatepicker:function(t,i,a,n,o){var u,c,h,l,d,p=this._dialogInst;return p||(this.uuid+=1,u="dp"+this.uuid,this._dialogInput=e(""),this._dialogInput.keydown(this._doKeyDown),e("body").append(this._dialogInput),p=this._dialogInst=this._newInst(this._dialogInput,!1),p.settings={},e.data(this._dialogInput[0],r,p)),s(p.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(p,i):i,this._dialogInput.val(i),this._pos=o?o.length?o:[o.pageX,o.pageY]:null,this._pos||(c=document.documentElement.clientWidth,h=document.documentElement.clientHeight,l=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[c/2-100+l,h/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),p.settings.onSelect=a,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),e.blockUI&&e.blockUI(this.dpDiv),e.data(this._dialogInput[0],r,p),this},_destroyDatepicker:function(t){var i,a=e(t),s=e.data(t,r);a.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),e.removeData(t,r),"input"===i?(s.append.remove(),s.trigger.remove(),a.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&a.removeClass(this.markerClassName).empty())},_enableDatepicker:function(t){var i,a,s=e(t),n=e.data(t,r);s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!1,n.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(a=s.children("."+this._inlineClass),a.children().removeClass("ui-state-disabled"),a.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}))},_disableDatepicker:function(t){var i,a,s=e(t),n=e.data(t,r);s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!0,n.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(a=s.children("."+this._inlineClass),a.children().addClass("ui-state-disabled"),a.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}),this._disabledInputs[this._disabledInputs.length]=t)},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;this._disabledInputs.length>t;t++)if(this._disabledInputs[t]===e)return!0;return!1},_getInst:function(t){try{return e.data(t,r)}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(i,a,n){var r,o,u,c,h=this._getInst(i);return 2===arguments.length&&"string"==typeof a?"defaults"===a?e.extend({},e.datepicker._defaults):h?"all"===a?e.extend({},h.settings):this._get(h,a):null:(r=a||{},"string"==typeof a&&(r={},r[a]=n),h&&(this._curInst===h&&this._hideDatepicker(),o=this._getDateDatepicker(i,!0),u=this._getMinMaxDate(h,"min"),c=this._getMinMaxDate(h,"max"),s(h.settings,r),null!==u&&r.dateFormat!==t&&r.minDate===t&&(h.settings.minDate=this._formatDate(h,u)),null!==c&&r.dateFormat!==t&&r.maxDate===t&&(h.settings.maxDate=this._formatDate(h,c)),"disabled"in r&&(r.disabled?this._disableDatepicker(i):this._enableDatepicker(i)),this._attachments(e(i),h),this._autoSize(h),this._setDate(h,o),this._updateAlternate(h),this._updateDatepicker(h)),t)},_changeDatepicker:function(e,t,i){this._optionDatepicker(e,t,i)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var i=this._getInst(e);i&&(this._setDate(i,t),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(e,t){var i=this._getInst(e);return i&&!i.inline&&this._setDateFromField(i,t),i?this._getDate(i):null},_doKeyDown:function(t){var i,a,s,n=e.datepicker._getInst(t.target),r=!0,o=n.dpDiv.is(".ui-datepicker-rtl");if(n._keyEvent=!0,e.datepicker._datepickerShowing)switch(t.keyCode){case 9:e.datepicker._hideDatepicker(),r=!1;break;case 13:return s=e("td."+e.datepicker._dayOverClass+":not(."+e.datepicker._currentClass+")",n.dpDiv),s[0]&&e.datepicker._selectDay(t.target,n.selectedMonth,n.selectedYear,s[0]),i=e.datepicker._get(n,"onSelect"),i?(a=e.datepicker._formatDate(n),i.apply(n.input?n.input[0]:null,[a,n])):e.datepicker._hideDatepicker(),!1;case 27:e.datepicker._hideDatepicker();break;case 33:e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(n,"stepBigMonths"):-e.datepicker._get(n,"stepMonths"),"M");break;case 34:e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(n,"stepBigMonths"):+e.datepicker._get(n,"stepMonths"),"M");break;case 35:(t.ctrlKey||t.metaKey)&&e.datepicker._clearDate(t.target),r=t.ctrlKey||t.metaKey;break;case 36:(t.ctrlKey||t.metaKey)&&e.datepicker._gotoToday(t.target),r=t.ctrlKey||t.metaKey;break;case 37:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,o?1:-1,"D"),r=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(n,"stepBigMonths"):-e.datepicker._get(n,"stepMonths"),"M");break;case 38:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,-7,"D"),r=t.ctrlKey||t.metaKey;break;case 39:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,o?-1:1,"D"),r=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(n,"stepBigMonths"):+e.datepicker._get(n,"stepMonths"),"M");break;case 40:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,7,"D"),r=t.ctrlKey||t.metaKey;break;default:r=!1}else 36===t.keyCode&&t.ctrlKey?e.datepicker._showDatepicker(this):r=!1;r&&(t.preventDefault(),t.stopPropagation())},_doKeyPress:function(i){var a,s,n=e.datepicker._getInst(i.target);return e.datepicker._get(n,"constrainInput")?(a=e.datepicker._possibleChars(e.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==i.charCode?i.keyCode:i.charCode),i.ctrlKey||i.metaKey||" ">s||!a||a.indexOf(s)>-1):t},_doKeyUp:function(t){var i,a=e.datepicker._getInst(t.target);if(a.input.val()!==a.lastVal)try{i=e.datepicker.parseDate(e.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,e.datepicker._getFormatConfig(a)),i&&(e.datepicker._setDateFromField(a),e.datepicker._updateAlternate(a),e.datepicker._updateDatepicker(a))}catch(s){}return!0},_showDatepicker:function(t){if(t=t.target||t,"input"!==t.nodeName.toLowerCase()&&(t=e("input",t.parentNode)[0]),!e.datepicker._isDisabledDatepicker(t)&&e.datepicker._lastInput!==t){var i,a,n,r,o,u,c;i=e.datepicker._getInst(t),e.datepicker._curInst&&e.datepicker._curInst!==i&&(e.datepicker._curInst.dpDiv.stop(!0,!0),i&&e.datepicker._datepickerShowing&&e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])),a=e.datepicker._get(i,"beforeShow"),n=a?a.apply(t,[t,i]):{},n!==!1&&(s(i.settings,n),i.lastVal=null,e.datepicker._lastInput=t,e.datepicker._setDateFromField(i),e.datepicker._inDialog&&(t.value=""),e.datepicker._pos||(e.datepicker._pos=e.datepicker._findPos(t),e.datepicker._pos[1]+=t.offsetHeight),r=!1,e(t).parents().each(function(){return r|="fixed"===e(this).css("position"),!r}),o={left:e.datepicker._pos[0],top:e.datepicker._pos[1]},e.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),e.datepicker._updateDatepicker(i),o=e.datepicker._checkOffset(i,o,r),i.dpDiv.css({position:e.datepicker._inDialog&&e.blockUI?"static":r?"fixed":"absolute",display:"none",left:o.left+"px",top:o.top+"px"}),i.inline||(u=e.datepicker._get(i,"showAnim"),c=e.datepicker._get(i,"duration"),i.dpDiv.zIndex(e(t).zIndex()+1),e.datepicker._datepickerShowing=!0,e.effects&&e.effects.effect[u]?i.dpDiv.show(u,e.datepicker._get(i,"showOptions"),c):i.dpDiv[u||"show"](u?c:null),e.datepicker._shouldFocusInput(i)&&i.input.focus(),e.datepicker._curInst=i))}},_updateDatepicker:function(t){this.maxRows=4,n=t,t.dpDiv.empty().append(this._generateHTML(t)),this._attachHandlers(t),t.dpDiv.find("."+this._dayOverClass+" a").mouseover();var i,a=this._getNumberOfMonths(t),s=a[1],r=17;t.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),s>1&&t.dpDiv.addClass("ui-datepicker-multi-"+s).css("width",r*s+"em"),t.dpDiv[(1!==a[0]||1!==a[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),t.dpDiv[(this._get(t,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),t===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(t)&&t.input.focus(),t.yearshtml&&(i=t.yearshtml,setTimeout(function(){i===t.yearshtml&&t.yearshtml&&t.dpDiv.find("select.ui-datepicker-year:first").replaceWith(t.yearshtml),i=t.yearshtml=null},0))},_shouldFocusInput:function(e){return e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&!e.input.is(":focus")},_checkOffset:function(t,i,a){var s=t.dpDiv.outerWidth(),n=t.dpDiv.outerHeight(),r=t.input?t.input.outerWidth():0,o=t.input?t.input.outerHeight():0,u=document.documentElement.clientWidth+(a?0:e(document).scrollLeft()),c=document.documentElement.clientHeight+(a?0:e(document).scrollTop());return i.left-=this._get(t,"isRTL")?s-r:0,i.left-=a&&i.left===t.input.offset().left?e(document).scrollLeft():0,i.top-=a&&i.top===t.input.offset().top+o?e(document).scrollTop():0,i.left-=Math.min(i.left,i.left+s>u&&u>s?Math.abs(i.left+s-u):0),i.top-=Math.min(i.top,i.top+n>c&&c>n?Math.abs(n+o):0),i},_findPos:function(t){for(var i,a=this._getInst(t),s=this._get(a,"isRTL");t&&("hidden"===t.type||1!==t.nodeType||e.expr.filters.hidden(t));)t=t[s?"previousSibling":"nextSibling"];return i=e(t).offset(),[i.left,i.top]},_hideDatepicker:function(t){var i,a,s,n,o=this._curInst;!o||t&&o!==e.data(t,r)||this._datepickerShowing&&(i=this._get(o,"showAnim"),a=this._get(o,"duration"),s=function(){e.datepicker._tidyDialog(o)},e.effects&&(e.effects.effect[i]||e.effects[i])?o.dpDiv.hide(i,e.datepicker._get(o,"showOptions"),a,s):o.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?a:null,s),i||s(),this._datepickerShowing=!1,n=this._get(o,"onClose"),n&&n.apply(o.input?o.input[0]:null,[o.input?o.input.val():"",o]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),e.blockUI&&(e.unblockUI(),e("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(t){if(e.datepicker._curInst){var i=e(t.target),a=e.datepicker._getInst(i[0]);(i[0].id!==e.datepicker._mainDivId&&0===i.parents("#"+e.datepicker._mainDivId).length&&!i.hasClass(e.datepicker.markerClassName)&&!i.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&(!e.datepicker._inDialog||!e.blockUI)||i.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==a)&&e.datepicker._hideDatepicker()}},_adjustDate:function(t,i,a){var s=e(t),n=this._getInst(s[0]);this._isDisabledDatepicker(s[0])||(this._adjustInstDate(n,i+("M"===a?this._get(n,"showCurrentAtPos"):0),a),this._updateDatepicker(n))},_gotoToday:function(t){var i,a=e(t),s=this._getInst(a[0]);this._get(s,"gotoCurrent")&&s.currentDay?(s.selectedDay=s.currentDay,s.drawMonth=s.selectedMonth=s.currentMonth,s.drawYear=s.selectedYear=s.currentYear):(i=new Date,s.selectedDay=i.getDate(),s.drawMonth=s.selectedMonth=i.getMonth(),s.drawYear=s.selectedYear=i.getFullYear()),this._notifyChange(s),this._adjustDate(a)},_selectMonthYear:function(t,i,a){var s=e(t),n=this._getInst(s[0]);n["selected"+("M"===a?"Month":"Year")]=n["draw"+("M"===a?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(n),this._adjustDate(s)},_selectDay:function(t,i,a,s){var n,r=e(t);e(s).hasClass(this._unselectableClass)||this._isDisabledDatepicker(r[0])||(n=this._getInst(r[0]),n.selectedDay=n.currentDay=e("a",s).html(),n.selectedMonth=n.currentMonth=i,n.selectedYear=n.currentYear=a,this._selectDate(t,this._formatDate(n,n.currentDay,n.currentMonth,n.currentYear)))},_clearDate:function(t){var i=e(t);this._selectDate(i,"")},_selectDate:function(t,i){var a,s=e(t),n=this._getInst(s[0]);i=null!=i?i:this._formatDate(n),n.input&&n.input.val(i),this._updateAlternate(n),a=this._get(n,"onSelect"),a?a.apply(n.input?n.input[0]:null,[i,n]):n.input&&n.input.trigger("change"),n.inline?this._updateDatepicker(n):(this._hideDatepicker(),this._lastInput=n.input[0],"object"!=typeof n.input[0]&&n.input.focus(),this._lastInput=null)},_updateAlternate:function(t){var i,a,s,n=this._get(t,"altField");n&&(i=this._get(t,"altFormat")||this._get(t,"dateFormat"),a=this._getDate(t),s=this.formatDate(i,a,this._getFormatConfig(t)),e(n).each(function(){e(this).val(s)}))},noWeekends:function(e){var t=e.getDay();return[t>0&&6>t,""]},iso8601Week:function(e){var t,i=new Date(e.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),t=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((t-i)/864e5)/7)+1},parseDate:function(i,a,s){if(null==i||null==a)throw"Invalid arguments";if(a="object"==typeof a?""+a:a+"",""===a)return null;var n,r,o,u,c=0,h=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,l="string"!=typeof h?h:(new Date).getFullYear()%100+parseInt(h,10),d=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,p=(s?s.dayNames:null)||this._defaults.dayNames,g=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,m=(s?s.monthNames:null)||this._defaults.monthNames,f=-1,_=-1,v=-1,k=-1,y=!1,b=function(e){var t=i.length>n+1&&i.charAt(n+1)===e;return t&&n++,t},D=function(e){var t=b(e),i="@"===e?14:"!"===e?20:"y"===e&&t?4:"o"===e?3:2,s=RegExp("^\\d{1,"+i+"}"),n=a.substring(c).match(s);if(!n)throw"Missing number at position "+c;return c+=n[0].length,parseInt(n[0],10)},w=function(i,s,n){var r=-1,o=e.map(b(i)?n:s,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)});if(e.each(o,function(e,i){var s=i[1];return a.substr(c,s.length).toLowerCase()===s.toLowerCase()?(r=i[0],c+=s.length,!1):t}),-1!==r)return r+1;throw"Unknown name at position "+c},M=function(){if(a.charAt(c)!==i.charAt(n))throw"Unexpected literal at position "+c;c++};for(n=0;i.length>n;n++)if(y)"'"!==i.charAt(n)||b("'")?M():y=!1;else switch(i.charAt(n)){case"d":v=D("d");break;case"D":w("D",d,p);break;case"o":k=D("o");break;case"m":_=D("m");break;case"M":_=w("M",g,m);break;case"y":f=D("y");break;case"@":u=new Date(D("@")),f=u.getFullYear(),_=u.getMonth()+1,v=u.getDate();break;case"!":u=new Date((D("!")-this._ticksTo1970)/1e4),f=u.getFullYear(),_=u.getMonth()+1,v=u.getDate();break;case"'":b("'")?M():y=!0;break;default:M()}if(a.length>c&&(o=a.substr(c),!/^\s+/.test(o)))throw"Extra/unparsed characters found in date: "+o;if(-1===f?f=(new Date).getFullYear():100>f&&(f+=(new Date).getFullYear()-(new Date).getFullYear()%100+(l>=f?0:-100)),k>-1)for(_=1,v=k;;){if(r=this._getDaysInMonth(f,_-1),r>=v)break;_++,v-=r}if(u=this._daylightSavingAdjust(new Date(f,_-1,v)),u.getFullYear()!==f||u.getMonth()+1!==_||u.getDate()!==v)throw"Invalid date";return u},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(e,t,i){if(!t)return"";var a,s=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,n=(i?i.dayNames:null)||this._defaults.dayNames,r=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,o=(i?i.monthNames:null)||this._defaults.monthNames,u=function(t){var i=e.length>a+1&&e.charAt(a+1)===t;return i&&a++,i},c=function(e,t,i){var a=""+t;if(u(e))for(;i>a.length;)a="0"+a;return a},h=function(e,t,i,a){return u(e)?a[t]:i[t]},l="",d=!1;if(t)for(a=0;e.length>a;a++)if(d)"'"!==e.charAt(a)||u("'")?l+=e.charAt(a):d=!1;else switch(e.charAt(a)){case"d":l+=c("d",t.getDate(),2);break;case"D":l+=h("D",t.getDay(),s,n);break;case"o":l+=c("o",Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()-new Date(t.getFullYear(),0,0).getTime())/864e5),3);break;case"m":l+=c("m",t.getMonth()+1,2);break;case"M":l+=h("M",t.getMonth(),r,o);break;case"y":l+=u("y")?t.getFullYear():(10>t.getYear()%100?"0":"")+t.getYear()%100;break;case"@":l+=t.getTime();break;case"!":l+=1e4*t.getTime()+this._ticksTo1970;break;case"'":u("'")?l+="'":d=!0;break;default:l+=e.charAt(a)}return l},_possibleChars:function(e){var t,i="",a=!1,s=function(i){var a=e.length>t+1&&e.charAt(t+1)===i;return a&&t++,a};for(t=0;e.length>t;t++)if(a)"'"!==e.charAt(t)||s("'")?i+=e.charAt(t):a=!1;else switch(e.charAt(t)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":s("'")?i+="'":a=!0;break;default:i+=e.charAt(t)}return i},_get:function(e,i){return e.settings[i]!==t?e.settings[i]:this._defaults[i]},_setDateFromField:function(e,t){if(e.input.val()!==e.lastVal){var i=this._get(e,"dateFormat"),a=e.lastVal=e.input?e.input.val():null,s=this._getDefaultDate(e),n=s,r=this._getFormatConfig(e);try{n=this.parseDate(i,a,r)||s}catch(o){a=t?"":a}e.selectedDay=n.getDate(),e.drawMonth=e.selectedMonth=n.getMonth(),e.drawYear=e.selectedYear=n.getFullYear(),e.currentDay=a?n.getDate():0,e.currentMonth=a?n.getMonth():0,e.currentYear=a?n.getFullYear():0,this._adjustInstDate(e)}},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(t,i,a){var s=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},n=function(i){try{return e.datepicker.parseDate(e.datepicker._get(t,"dateFormat"),i,e.datepicker._getFormatConfig(t))}catch(a){}for(var s=(i.toLowerCase().match(/^c/)?e.datepicker._getDate(t):null)||new Date,n=s.getFullYear(),r=s.getMonth(),o=s.getDate(),u=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,c=u.exec(i);c;){switch(c[2]||"d"){case"d":case"D":o+=parseInt(c[1],10);break;case"w":case"W":o+=7*parseInt(c[1],10);break;case"m":case"M":r+=parseInt(c[1],10),o=Math.min(o,e.datepicker._getDaysInMonth(n,r));break;case"y":case"Y":n+=parseInt(c[1],10),o=Math.min(o,e.datepicker._getDaysInMonth(n,r))}c=u.exec(i)}return new Date(n,r,o)},r=null==i||""===i?a:"string"==typeof i?n(i):"number"==typeof i?isNaN(i)?a:s(i):new Date(i.getTime());return r=r&&"Invalid Date"==""+r?a:r,r&&(r.setHours(0),r.setMinutes(0),r.setSeconds(0),r.setMilliseconds(0)),this._daylightSavingAdjust(r)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,i){var a=!t,s=e.selectedMonth,n=e.selectedYear,r=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=r.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=r.getMonth(),e.drawYear=e.selectedYear=e.currentYear=r.getFullYear(),s===e.selectedMonth&&n===e.selectedYear||i||this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(a?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&""===e.input.val()?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(t){var i=this._get(t,"stepMonths"),a="#"+t.id.replace(/\\\\/g,"\\");t.dpDiv.find("[data-handler]").map(function(){var t={prev:function(){e.datepicker._adjustDate(a,-i,"M")},next:function(){e.datepicker._adjustDate(a,+i,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(a)},selectDay:function(){return e.datepicker._selectDay(a,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return e.datepicker._selectMonthYear(a,this,"M"),!1},selectYear:function(){return e.datepicker._selectMonthYear(a,this,"Y"),!1}};e(this).bind(this.getAttribute("data-event"),t[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t,i,a,s,n,r,o,u,c,h,l,d,p,g,m,f,_,v,k,y,b,D,w,M,C,x,I,N,T,A,E,S,Y,F,P,O,j,K,R,H=new Date,W=this._daylightSavingAdjust(new Date(H.getFullYear(),H.getMonth(),H.getDate())),L=this._get(e,"isRTL"),U=this._get(e,"showButtonPanel"),B=this._get(e,"hideIfNoPrevNext"),z=this._get(e,"navigationAsDateFormat"),q=this._getNumberOfMonths(e),G=this._get(e,"showCurrentAtPos"),J=this._get(e,"stepMonths"),Q=1!==q[0]||1!==q[1],V=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),$=this._getMinMaxDate(e,"min"),X=this._getMinMaxDate(e,"max"),Z=e.drawMonth-G,et=e.drawYear;if(0>Z&&(Z+=12,et--),X)for(t=this._daylightSavingAdjust(new Date(X.getFullYear(),X.getMonth()-q[0]*q[1]+1,X.getDate())),t=$&&$>t?$:t;this._daylightSavingAdjust(new Date(et,Z,1))>t;)Z--,0>Z&&(Z=11,et--);for(e.drawMonth=Z,e.drawYear=et,i=this._get(e,"prevText"),i=z?this.formatDate(i,this._daylightSavingAdjust(new Date(et,Z-J,1)),this._getFormatConfig(e)):i,a=this._canAdjustMonth(e,-1,et,Z)?"
      "+i+"":B?"":""+i+"",s=this._get(e,"nextText"),s=z?this.formatDate(s,this._daylightSavingAdjust(new Date(et,Z+J,1)),this._getFormatConfig(e)):s,n=this._canAdjustMonth(e,1,et,Z)?""+s+"":B?"":""+s+"",r=this._get(e,"currentText"),o=this._get(e,"gotoCurrent")&&e.currentDay?V:W,r=z?this.formatDate(r,o,this._getFormatConfig(e)):r,u=e.inline?"":"",c=U?"
      "+(L?u:"")+(this._isInRange(e,o)?"":"")+(L?"":u)+"
      ":"",h=parseInt(this._get(e,"firstDay"),10),h=isNaN(h)?0:h,l=this._get(e,"showWeek"),d=this._get(e,"dayNames"),p=this._get(e,"dayNamesMin"),g=this._get(e,"monthNames"),m=this._get(e,"monthNamesShort"),f=this._get(e,"beforeShowDay"),_=this._get(e,"showOtherMonths"),v=this._get(e,"selectOtherMonths"),k=this._getDefaultDate(e),y="",D=0;q[0]>D;D++){for(w="",this.maxRows=4,M=0;q[1]>M;M++){if(C=this._daylightSavingAdjust(new Date(et,Z,e.selectedDay)),x=" ui-corner-all",I="",Q){if(I+="
      "}for(I+="
      "+(/all|left/.test(x)&&0===D?L?n:a:"")+(/all|right/.test(x)&&0===D?L?a:n:"")+this._generateMonthYearHeader(e,Z,et,$,X,D>0||M>0,g,m)+"
      "+"",N=l?"":"",b=0;7>b;b++)T=(b+h)%7,N+="=5?" class='ui-datepicker-week-end'":"")+">"+""+p[T]+"";for(I+=N+"",A=this._getDaysInMonth(et,Z),et===e.selectedYear&&Z===e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,A)),E=(this._getFirstDayOfMonth(et,Z)-h+7)%7,S=Math.ceil((E+A)/7),Y=Q?this.maxRows>S?this.maxRows:S:S,this.maxRows=Y,F=this._daylightSavingAdjust(new Date(et,Z,1-E)),P=0;Y>P;P++){for(I+="",O=l?"":"",b=0;7>b;b++)j=f?f.apply(e.input?e.input[0]:null,[F]):[!0,""],K=F.getMonth()!==Z,R=K&&!v||!j[0]||$&&$>F||X&&F>X,O+="",F.setDate(F.getDate()+1),F=this._daylightSavingAdjust(F);I+=O+""}Z++,Z>11&&(Z=0,et++),I+="
      "+this._get(e,"weekHeader")+"
      "+this._get(e,"calculateWeek")(F)+""+(K&&!_?" ":R?""+F.getDate()+"":""+F.getDate()+"")+"
      "+(Q?"
      "+(q[0]>0&&M===q[1]-1?"
      ":""):""),w+=I}y+=w}return y+=c,e._keyEvent=!1,y},_generateMonthYearHeader:function(e,t,i,a,s,n,r,o){var u,c,h,l,d,p,g,m,f=this._get(e,"changeMonth"),_=this._get(e,"changeYear"),v=this._get(e,"showMonthAfterYear"),k="
      ",y="";if(n||!f)y+=""+r[t]+"";else{for(u=a&&a.getFullYear()===i,c=s&&s.getFullYear()===i,y+=""}if(v||(k+=y+(!n&&f&&_?"":" ")),!e.yearshtml)if(e.yearshtml="",n||!_)k+=""+i+"";else{for(l=this._get(e,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(e){var t=e.match(/c[+\-].*/)?i+parseInt(e.substring(1),10):e.match(/[+\-].*/)?d+parseInt(e,10):parseInt(e,10); -return isNaN(t)?d:t},g=p(l[0]),m=Math.max(g,p(l[1]||"")),g=a?Math.max(g,a.getFullYear()):g,m=s?Math.min(m,s.getFullYear()):m,e.yearshtml+="",k+=e.yearshtml,e.yearshtml=null}return k+=this._get(e,"yearSuffix"),v&&(k+=(!n&&f&&_?"":" ")+y),k+="
      "},_adjustInstDate:function(e,t,i){var a=e.drawYear+("Y"===i?t:0),s=e.drawMonth+("M"===i?t:0),n=Math.min(e.selectedDay,this._getDaysInMonth(a,s))+("D"===i?t:0),r=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(a,s,n)));e.selectedDay=r.getDate(),e.drawMonth=e.selectedMonth=r.getMonth(),e.drawYear=e.selectedYear=r.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(e)},_restrictMinMax:function(e,t){var i=this._getMinMaxDate(e,"min"),a=this._getMinMaxDate(e,"max"),s=i&&i>t?i:t;return a&&s>a?a:s},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return null==t?[1,1]:"number"==typeof t?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return new Date(e,t,1).getDay()},_canAdjustMonth:function(e,t,i,a){var s=this._getNumberOfMonths(e),n=this._daylightSavingAdjust(new Date(i,a+(0>t?t:s[0]*s[1]),1));return 0>t&&n.setDate(this._getDaysInMonth(n.getFullYear(),n.getMonth())),this._isInRange(e,n)},_isInRange:function(e,t){var i,a,s=this._getMinMaxDate(e,"min"),n=this._getMinMaxDate(e,"max"),r=null,o=null,u=this._get(e,"yearRange");return u&&(i=u.split(":"),a=(new Date).getFullYear(),r=parseInt(i[0],10),o=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(r+=a),i[1].match(/[+\-].*/)&&(o+=a)),(!s||t.getTime()>=s.getTime())&&(!n||t.getTime()<=n.getTime())&&(!r||t.getFullYear()>=r)&&(!o||o>=t.getFullYear())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t="string"!=typeof t?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,i,a){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var s=t?"object"==typeof t?t:this._daylightSavingAdjust(new Date(a,i,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),s,this._getFormatConfig(e))}}),e.fn.datepicker=function(t){if(!this.length)return this;e.datepicker.initialized||(e(document).mousedown(e.datepicker._checkExternalClick),e.datepicker.initialized=!0),0===e("#"+e.datepicker._mainDivId).length&&e("body").append(e.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof t||"isDisabled"!==t&&"getDate"!==t&&"widget"!==t?"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof t?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this].concat(i)):e.datepicker._attachDatepicker(this,t)}):e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i))},e.datepicker=new i,e.datepicker.initialized=!1,e.datepicker.uuid=(new Date).getTime(),e.datepicker.version="1.10.4"})(jQuery);(function(t){t.widget("ui.menu",{version:"1.10.4",defaultElement:"
        ",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,t.proxy(function(t){this.options.disabled&&t.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(t){t.preventDefault()},"click .ui-state-disabled > a":function(t){t.preventDefault()},"click .ui-menu-item:has(a)":function(e){var i=t(e.target).closest(".ui-menu-item");!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i=t(e.currentTarget);i.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(e,i)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.children(".ui-menu-item").eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){t(e.target).closest(".ui-menu").length||this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){function i(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var s,n,a,o,r,l=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:l=!1,n=this.previousFilter||"",a=String.fromCharCode(e.keyCode),o=!1,clearTimeout(this.filterTimer),a===n?o=!0:a=n+a,r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())}),s=o&&-1!==s.index(this.active.next())?this.active.nextAll(".ui-menu-item"):s,s.length||(a=String.fromCharCode(e.keyCode),r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())})),s.length?(this.focus(e,s),s.length>1?(this.previousFilter=a,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}l&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i=this.options.icons.submenu,s=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),s.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),s=e.prev("a"),n=t("").addClass("ui-menu-icon ui-icon "+i).data("ui-menu-submenu-carat",!0);s.attr("aria-haspopup","true").prepend(n),e.attr("aria-labelledby",s.attr("id"))}),e=s.add(this.element),e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),e.children(":not(.ui-menu-item)").each(function(){var e=t(this);/[^\-\u2014\u2013\s]/.test(e.text())||e.addClass("ui-widget-content ui-menu-divider")}),e.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),this._super(t,e)},focus:function(t,e){var i,s;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=e.height(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.children(".ui-menu-item")[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())),undefined):(this.next(e),undefined)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item").first())),undefined):(this.next(e),undefined)},_hasScroll:function(){return this.element.outerHeight()>label: Widget +//>>group: Core +//>>description: Provides a factory for creating stateful widgets with a common API. +//>>docs: http://api.jqueryui.com/jQuery.widget/ +//>>demos: http://jqueryui.com/widget/ + + + +var widgetUuid = 0; +var widgetSlice = Array.prototype.slice; + +$.cleanData = ( function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // Http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +} )( $.cleanData ); + +$.widget = function( name, base, prototype ) { + var existingConstructor, constructor, basePrototype; + + // ProxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + var proxiedPrototype = {}; + + var namespace = name.split( "." )[ 0 ]; + name = name.split( "." )[ 1 ]; + var fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + if ( $.isArray( prototype ) ) { + prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); + } + + // Create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + + // Allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // Allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + // Extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + + // Copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + + // Track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + } ); + + basePrototype = new base(); + + // We need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = ( function() { + function _super() { + return base.prototype[ prop ].apply( this, arguments ); + } + + function _superApply( args ) { + return base.prototype[ prop ].apply( this, args ); + } + + return function() { + var __super = this._super; + var __superApply = this._superApply; + var returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + } )(); + } ); + constructor.prototype = $.widget.extend( basePrototype, { + + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + } ); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // Redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, + child._proto ); + } ); + + // Remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = widgetSlice.call( arguments, 1 ); + var inputIndex = 0; + var inputLength = input.length; + var key; + var value; + + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string"; + var args = widgetSlice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + this.each( function() { + var methodValue; + var instance = $.data( this, fullName ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on " + name + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + } ); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
        ", + + options: { + classes: {}, + disabled: false, + + // Callbacks + create: null + }, + + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widgetUuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + this.classesElementLookup = {}; + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + } ); + this.document = $( element.style ? + + // Element within the document + element.ownerDocument : + + // Element is window or document + element.document || element ); + this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + + if ( this.options.disabled ) { + this._setOptionDisabled( this.options.disabled ); + } + + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + + _getCreateOptions: function() { + return {}; + }, + + _getCreateEventData: $.noop, + + _create: $.noop, + + _init: $.noop, + + destroy: function() { + var that = this; + + this._destroy(); + $.each( this.classesElementLookup, function( key, value ) { + that._removeClass( value, key ); + } ); + + // We can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .off( this.eventNamespace ) + .removeData( this.widgetFullName ); + this.widget() + .off( this.eventNamespace ) + .removeAttr( "aria-disabled" ); + + // Clean up events and states + this.bindings.off( this.eventNamespace ); + }, + + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + var parts; + var curOption; + var i; + + if ( arguments.length === 0 ) { + + // Don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + + // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + + _setOption: function( key, value ) { + if ( key === "classes" ) { + this._setOptionClasses( value ); + } + + this.options[ key ] = value; + + if ( key === "disabled" ) { + this._setOptionDisabled( value ); + } + + return this; + }, + + _setOptionClasses: function( value ) { + var classKey, elements, currentElements; + + for ( classKey in value ) { + currentElements = this.classesElementLookup[ classKey ]; + if ( value[ classKey ] === this.options.classes[ classKey ] || + !currentElements || + !currentElements.length ) { + continue; + } + + // We are doing this to create a new jQuery object because the _removeClass() call + // on the next line is going to destroy the reference to the current elements being + // tracked. We need to save a copy of this collection so that we can add the new classes + // below. + elements = $( currentElements.get() ); + this._removeClass( currentElements, classKey ); + + // We don't use _addClass() here, because that uses this.options.classes + // for generating the string of classes. We want to use the value passed in from + // _setOption(), this is the new value of the classes option which was passed to + // _setOption(). We pass this value directly to _classes(). + elements.addClass( this._classes( { + element: elements, + keys: classKey, + classes: value, + add: true + } ) ); + } + }, + + _setOptionDisabled: function( value ) { + this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this._removeClass( this.hoverable, null, "ui-state-hover" ); + this._removeClass( this.focusable, null, "ui-state-focus" ); + } + }, + + enable: function() { + return this._setOptions( { disabled: false } ); + }, + + disable: function() { + return this._setOptions( { disabled: true } ); + }, + + _classes: function( options ) { + var full = []; + var that = this; + + options = $.extend( { + element: this.element, + classes: this.options.classes || {} + }, options ); + + function processClassString( classes, checkOption ) { + var current, i; + for ( i = 0; i < classes.length; i++ ) { + current = that.classesElementLookup[ classes[ i ] ] || $(); + if ( options.add ) { + current = $( $.unique( current.get().concat( options.element.get() ) ) ); + } else { + current = $( current.not( options.element ).get() ); + } + that.classesElementLookup[ classes[ i ] ] = current; + full.push( classes[ i ] ); + if ( checkOption && options.classes[ classes[ i ] ] ) { + full.push( options.classes[ classes[ i ] ] ); + } + } + } + + if ( options.keys ) { + processClassString( options.keys.match( /\S+/g ) || [], true ); + } + if ( options.extra ) { + processClassString( options.extra.match( /\S+/g ) || [] ); + } + + return full.join( " " ); + }, + + _removeClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, false ); + }, + + _addClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, true ); + }, + + _toggleClass: function( element, keys, extra, add ) { + add = ( typeof add === "boolean" ) ? add : extra; + var shift = ( typeof element === "string" || element === null ), + options = { + extra: shift ? keys : extra, + keys: shift ? element : keys, + element: shift ? this.element : element, + add: add + }; + options.element.toggleClass( this._classes( options ), add ); + return this; + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement; + var instance = this; + + // No suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // No element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + + // Allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // Copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ); + var eventName = match[ 1 ] + instance.eventNamespace; + var selector = match[ 2 ]; + + if ( selector ) { + delegateElement.on( eventName, selector, handlerProxy ); + } else { + element.on( eventName, handlerProxy ); + } + } ); + }, + + _off: function( element, eventName ) { + eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.off( eventName ).off( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); + }, + mouseleave: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); + } + } ); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); + }, + focusout: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); + } + } ); + }, + + _trigger: function( type, event, data ) { + var prop, orig; + var callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + + // The original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // Copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + + var hasOptions; + var effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + + if ( options.delay ) { + element.delay( options.delay ); + } + + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue( function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + } ); + } + }; +} ); + +var widget = $.widget; + + +/*! + * jQuery UI Position 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/position/ + */ + +//>>label: Position +//>>group: Core +//>>description: Positions elements relative to other elements. +//>>docs: http://api.jqueryui.com/position/ +//>>demos: http://jqueryui.com/position/ + + +( function() { +var cachedScrollbarWidth, supportsOffsetFractions, + max = Math.max, + abs = Math.abs, + round = Math.round, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + +// Support: IE <=9 only +supportsOffsetFractions = function() { + var element = $( "
        " ) + .css( "position", "absolute" ) + .appendTo( "body" ) + .offset( { + top: 1.5, + left: 1.5 + } ), + support = element.offset().top === 1.5; + + element.remove(); + + supportsOffsetFractions = function() { + return support; + }; + + return support; +}; + +function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; +} + +function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; +} + +function getDimensions( elem ) { + var raw = elem[ 0 ]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( $.isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; +} + +$.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "
        " + + "
        " ), + innerDiv = div.children()[ 0 ]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[ 0 ].clientWidth; + } + + div.remove(); + + return ( cachedScrollbarWidth = w1 - w2 ); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-x" ), + overflowY = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isWindow = $.isWindow( withinElement[ 0 ] ), + isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, + hasOffset = !isWindow && !isDocument; + return { + element: withinElement, + isWindow: isWindow, + isDocument: isDocument, + offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + width: withinElement.outerWidth(), + height: withinElement.outerHeight() + }; + } +}; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // Make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $( options.of ), + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[ 0 ].preventDefault ) { + + // Force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + + // Clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // Force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1 ) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // Calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // Reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + } ); + + // Normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each( function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + // If the browser doesn't support fractions, then round for consistent results + if ( !supportsOffsetFractions() ) { + position.left = round( position.left ); + position.top = round( position.top ); + } + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem: elem + } ); + } + } ); + + if ( options.using ) { + + // Adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + } ); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // Element is wider than within + if ( data.collisionWidth > outerWidth ) { + + // Element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - + withinOffset; + position.left += overLeft - newOverRight; + + // Element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + + // Element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + + // Too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + + // Too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + + // Adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // Element is taller than within + if ( data.collisionHeight > outerHeight ) { + + // Element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - + withinOffset; + position.top += overTop - newOverBottom; + + // Element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + + // Element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + + // Too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + + // Too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + + // Adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - + outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - + outerHeight - withinOffset; + if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { + position.top += myOffset + atOffset + offset; + } + } else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + + offset - offsetTop; + if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } +}; + +} )(); + +var position = $.ui.position; + + +/*! + * jQuery UI :data 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: :data Selector +//>>group: Core +//>>description: Selects elements which have data stored under the specified key. +//>>docs: http://api.jqueryui.com/data-selector/ + + +var data = $.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo( function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + } ) : + + // Support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + } +} ); + +/*! + * jQuery UI Disable Selection 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: disableSelection +//>>group: Core +//>>description: Disable selection of text content within the set of matched elements. +//>>docs: http://api.jqueryui.com/disableSelection/ + +// This file is deprecated + + +var disableSelection = $.fn.extend( { + disableSelection: ( function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; + + return function() { + return this.on( eventType + ".ui-disableSelection", function( event ) { + event.preventDefault(); + } ); + }; + } )(), + + enableSelection: function() { + return this.off( ".ui-disableSelection" ); + } +} ); + + +/*! + * jQuery UI Keycode 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Keycode +//>>group: Core +//>>description: Provide keycodes as keynames +//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ + + +var keycode = $.ui.keyCode = { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 +}; + + +/*! + * jQuery UI Scroll Parent 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: scrollParent +//>>group: Core +//>>description: Get the closest ancestor element that is scrollable. +//>>docs: http://api.jqueryui.com/scrollParent/ + + + +var scrollParent = $.fn.scrollParent = function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; + } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + + parent.css( "overflow-x" ) ); + } ).eq( 0 ); + + return position === "fixed" || !scrollParent.length ? + $( this[ 0 ].ownerDocument || document ) : + scrollParent; +}; + + +/*! + * jQuery UI Unique ID 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: uniqueId +//>>group: Core +//>>description: Functions to generate and remove uniqueId's +//>>docs: http://api.jqueryui.com/uniqueId/ + + + +var uniqueId = $.fn.extend( { + uniqueId: ( function() { + var uuid = 0; + + return function() { + return this.each( function() { + if ( !this.id ) { + this.id = "ui-id-" + ( ++uuid ); + } + } ); + }; + } )(), + + removeUniqueId: function() { + return this.each( function() { + if ( /^ui-id-\d+$/.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + } ); + } +} ); + + + + +// This file is deprecated +var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +/*! + * jQuery UI Mouse 1.12.0 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Mouse +//>>group: Widgets +//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. +//>>docs: http://api.jqueryui.com/mouse/ + + + +var mouseHandled = false; +$( document ).on( "mouseup", function() { + mouseHandled = false; +} ); + +var widgetsMouse = $.widget( "ui.mouse", { + version: "1.12.0", + options: { + cancel: "input, textarea, button, select, option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .on( "mousedown." + this.widgetName, function( event ) { + return that._mouseDown( event ); + } ) + .on( "click." + this.widgetName, function( event ) { + if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, that.widgetName + ".preventClickEvent" ); + event.stopImmediatePropagation(); + return false; + } + } ); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.off( "." + this.widgetName ); + if ( this._mouseMoveDelegate ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + } + }, + + _mouseDown: function( event ) { + + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // We may have missed mouseup (out of window) + ( this._mouseStarted && this._mouseUp( event ) ); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = ( event.which === 1 ), + + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? + $( event.target ).closest( this.options.cancel ).length : false ); + if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if ( !this.mouseDelayMet ) { + this._mouseDelayTimer = setTimeout( function() { + that.mouseDelayMet = true; + }, this.options.delay ); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = ( this._mouseStart( event ) !== false ); + if ( !this._mouseStarted ) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, this.widgetName + ".preventClickEvent" ); + } + + // These delegates are required to keep context + this._mouseMoveDelegate = function( event ) { + return that._mouseMove( event ); + }; + this._mouseUpDelegate = function( event ) { + return that._mouseUp( event ); + }; + + this.document + .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function( event ) { + + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + + // IE mouseup check - mouseup happened when mouse was out of window + if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && + !event.button ) { + return this._mouseUp( event ); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + + // Support: Safari <=8 - 9 + // Safari sets which to 0 if you press any of the following keys + // during a drag (#14461) + if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || + event.originalEvent.metaKey || event.originalEvent.shiftKey ) { + this.ignoreMissingWhich = true; + } else if ( !this.ignoreMissingWhich ) { + return this._mouseUp( event ); + } + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if ( this._mouseStarted ) { + this._mouseDrag( event ); + return event.preventDefault(); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = + ( this._mouseStart( this._mouseDownEvent, event ) !== false ); + ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); + } + + return !this._mouseStarted; + }, + + _mouseUp: function( event ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if ( this._mouseStarted ) { + this._mouseStarted = false; + + if ( event.target === this._mouseDownEvent.target ) { + $.data( event.target, this.widgetName + ".preventClickEvent", true ); + } + + this._mouseStop( event ); + } + + if ( this._mouseDelayTimer ) { + clearTimeout( this._mouseDelayTimer ); + delete this._mouseDelayTimer; + } + + this.ignoreMissingWhich = false; + mouseHandled = false; + event.preventDefault(); + }, + + _mouseDistanceMet: function( event ) { + return ( Math.max( + Math.abs( this._mouseDownEvent.pageX - event.pageX ), + Math.abs( this._mouseDownEvent.pageY - event.pageY ) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function( /* event */ ) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function( /* event */ ) {}, + _mouseDrag: function( /* event */ ) {}, + _mouseStop: function( /* event */ ) {}, + _mouseCapture: function( /* event */ ) { return true; } +} ); + + + + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +var plugin = $.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || + instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + + + +var safeActiveElement = $.ui.safeActiveElement = function( document ) { + var activeElement; + + // Support: IE 9 only + // IE9 throws an "Unspecified error" accessing document.activeElement from an