diff --git a/install/data/navigation.json b/install/data/navigation.json index 5956c6b05f..0ec72805de 100644 --- a/install/data/navigation.json +++ b/install/data/navigation.json @@ -79,9 +79,7 @@ "textClass": "visible-xs-inline", "text": "\\[\\[global:header.search\\]\\]", "properties": { - "installed": { - "search": true - } + "searchInstalled": true } } ] \ No newline at end of file diff --git a/package.json b/package.json index 7eb551e233..23d231eea4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.0.0", + "version": "1.0.2", "homepage": "http://www.nodebb.org", "repository": { "type": "git", @@ -44,17 +44,17 @@ "mongodb": "~2.1.3", "morgan": "^1.3.2", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "3.0.9", - "nodebb-plugin-dbsearch": "1.0.0", + "nodebb-plugin-composer-default": "3.0.14", + "nodebb-plugin-dbsearch": "1.0.1", "nodebb-plugin-emoji-extended": "1.0.3", "nodebb-plugin-markdown": "4.0.17", - "nodebb-plugin-mentions": "1.0.18", + "nodebb-plugin-mentions": "1.0.20", "nodebb-plugin-soundpack-default": "0.1.6", - "nodebb-plugin-spam-be-gone": "0.4.5", + "nodebb-plugin-spam-be-gone": "0.4.6", "nodebb-rewards-essentials": "0.0.8", "nodebb-theme-lavender": "3.0.9", - "nodebb-theme-persona": "4.0.99", - "nodebb-theme-vanilla": "5.0.56", + "nodebb-theme-persona": "4.0.110", + "nodebb-theme-vanilla": "5.0.58", "nodebb-widget-essentials": "2.0.8", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", @@ -75,7 +75,7 @@ "socket.io-redis": "^1.0.0", "socketio-wildcard": "~0.3.0", "string": "^3.0.0", - "templates.js": "0.3.3", + "templates.js": "0.3.4", "toobusy-js": "^0.4.2", "uglify-js": "^2.6.0", "underscore": "^1.8.3", diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index d76e82b5b3..07c85aa19f 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -42,6 +42,7 @@ "change_username": "Change Username", "change_email": "Change Email", "edit": "Edit", + "edit-profile": "Edit Profile", "default_picture": "Default Icon", "uploaded_picture": "Uploaded Picture", "upload_new_picture": "Upload New Picture", diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index 26238f1101..c6da11cd88 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -155,12 +155,6 @@ define('admin/manage/category', [ } else { $('a[href="#analytics"]').on('shown.bs.tab', Category.setupGraphs); } - - // Fix the input field for the category name, as it can contain a translation key - var nameInput = $('#cid-' + ajaxify.data.cid + '-name'); - if (ajaxify.data.category.name !== nameInput.val()) { - $('#cid-' + ajaxify.data.category.cid + '-name').val(ajaxify.data.category.name); - } }; Category.setupPrivilegeTable = function() { diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index 58c5017540..06284e4e88 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -98,7 +98,7 @@ define('admin/manage/group', [ templates.parse('partials/groups/memberlist', 'members', {group: {isOwner: ajaxify.data.group.isOwner, members: [member]}}, function(html) { translator.translate(html, function(html) { - $('[component="groups/members"] tr').first().before(html); + $('[component="groups/members"] tbody').prepend(html); }); }); }); diff --git a/public/src/app.js b/public/src/app.js index 65276bc798..2d37b53a41 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -326,7 +326,10 @@ app.cacheBuster = null; return; } require(['translator'], function(translator) { - title = config.titleLayout.replace(/{/g, '{').replace(/}/g, '}').replace('{pageTitle}', title).replace('{browserTitle}', config.browserTitle); + title = config.titleLayout.replace(/{/g, '{').replace(/}/g, '}') + .replace('{pageTitle}', function() { return title; }) + .replace('{browserTitle}', function() { return config.browserTitle; }); + translator.translate(title, function(translated) { titleObj.titles[0] = translated; app.alternatingTitle(''); diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index 7d444bfd87..606a460dff 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -7,12 +7,6 @@ define('forum/groups/list', ['forum/infinitescroll'], function(infinitescroll) { Groups.init = function() { var groupsEl = $('#groups-list'); - groupsEl.on('click', '.list-cover', function() { - var groupSlug = $(this).parents('[data-slug]').attr('data-slug'); - - ajaxify.go('groups/' + groupSlug); - }); - infinitescroll.init(Groups.loadMoreGroups); // Group creation diff --git a/public/src/client/topic.js b/public/src/client/topic.js index c3e228f9ae..7a989f96aa 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -221,7 +221,9 @@ define('forum/topic', [ } else { span.html('').hide(); } - app.removeAlert('bookmark'); + if ($(window).scrollTop() > 300) { + app.removeAlert('bookmark'); + } } Topic.calculateIndex = function(index, elementCount) { diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index d65a1c7c47..6c8f58b55b 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -124,7 +124,7 @@ define('forum/topic/events', [ var editData = { editor: data.editor, - relativeEditTime: utils.toISOString(data.post.edited) + editedISO: utils.toISOString(data.post.edited) }; templates.parse('partials/topic/post-editor', editData, function(html) { diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 28701dd56a..b16a05a067 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -34,7 +34,6 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator return app.alertError(err); } data.posts.display_move_tools = data.posts.display_move_tools && index !== 0; - data.postSharing = data.postSharing.filter(function(share) { return share.activated === true; }); templates.parse('partials/topic/post-menu-list', data, function(html) { translator.translate(html, function(html) { @@ -65,17 +64,21 @@ define('forum/topic/postTools', ['share', 'navigator', 'components', 'translator }; function addVoteHandler() { - components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', function() { - loadDataAndCreateTooltip($(this).parent()); - }); + components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', loadDataAndCreateTooltip); } - function loadDataAndCreateTooltip(el) { - var pid = el.parents('[data-pid]').attr('data-pid'); + function loadDataAndCreateTooltip() { + var $this = $(this), + el = $this.parent(), + pid = el.parents('[data-pid]').attr('data-pid'); + + $this.off('mouseenter', loadDataAndCreateTooltip); socket.emit('posts.getUpvoters', [pid], function(err, data) { if (!err && data.length) { createTooltip(el, data[0]); } + + $this.on('mouseenter', loadDataAndCreateTooltip); }); } diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 3256f4d2f0..72c9708439 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -26,7 +26,7 @@ define('forum/topic/posts', [ 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_move_tools = ajaxify.data.privileges.isAdminOrMod; - post.display_post_menu = post.selfPost || ajaxify.data.privileges.isAdminOrMod; + post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); }); updatePostCounts(data.posts); @@ -50,9 +50,7 @@ define('forum/topic/posts', [ function onNewPostPagination(data) { function scrollToPost() { - if (ajaxify.data.scrollToMyPost) { - scrollToPostIfSelf(data.posts[0]); - } + scrollToPostIfSelf(data.posts[0]); } var posts = data.posts; diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index ea8fca978a..e860a0ff20 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -50,7 +50,8 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra taskbar.push('chat', modal.attr('UUID'), { title: username, - touid: data.message.fromUser.uid + touid: data.message.fromUser.uid, + roomId: data.roomId }); } } else { diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 8d6b213a1d..dd15d0dab6 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -20,7 +20,7 @@ if ((properties.loggedIn && !data.config.loggedIn) || (properties.globalMod && !data.isGlobalMod && !data.isAdmin) || (properties.adminOnly && !data.isAdmin) || - (properties.installed && properties.installed.search && !data.searchEnabled)) { + (properties.searchInstalled && !data.searchEnabled)) { return false; } } diff --git a/public/src/modules/navigator.js b/public/src/modules/navigator.js index b17db23a14..3ac5fc16e2 100644 --- a/public/src/modules/navigator.js +++ b/public/src/modules/navigator.js @@ -225,7 +225,7 @@ define('navigator', ['forum/pagination', 'components'], function(pagination, com scrollTo.parents('[component="post"]').addClass('highlight'); setTimeout(function() { scrollTo.parents('[component="post"]').removeClass('highlight'); - }, 3000); + }, 10000); } } diff --git a/public/src/modules/taskbar.js b/public/src/modules/taskbar.js index e664037c0d..7f5978250b 100644 --- a/public/src/modules/taskbar.js +++ b/public/src/modules/taskbar.js @@ -55,7 +55,7 @@ define('taskbar', function() { $(window).trigger('filter:taskbar.push', data); - if (!element.length) { + if (!element.length && data.module) { createTaskbar(data); } }; diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 5892f6dde8..b208a0af48 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -213,13 +213,14 @@ function insertLanguage(text, key, value, variables) { if (value) { - var variable; - for (var i = 1, ii = variables.length; i < ii; i++) { - variable = S(variables[i]).chompRight(']]').collapseWhitespace().decodeHTMLEntities().escapeHTML().s; - value = value.replace('%' + i, variable); - } + variables.forEach(function(variable, index) { + if (index > 0) { + variable = S(variable).chompRight(']]').collapseWhitespace().decodeHTMLEntities().escapeHTML().s; + value = value.replace('%' + index, function() { return variable; }); + } + }); - text = text.replace(key, value); + text = text.replace(key, function() { return value; }); } else { var string = key.split(':'); text = text.replace(key, string[string.length-1].replace(regexes.replace, '')); diff --git a/public/src/utils.js b/public/src/utils.js index 2c821c1701..68de6a3506 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -92,6 +92,23 @@ return str; }, + cleanUpTag: function(tag, maxLength) { + if (typeof tag !== 'string' || !tag.length ) { + return ''; + } + + tag = tag.trim().toLowerCase(); + // see https://github.com/NodeBB/NodeBB/issues/4378 + tag = tag.replace(/\u202E/gi, ''); + tag = tag.replace(/[,\/#!$%\^\*;:{}=_`<>'"~()?\|]/g, ''); + tag = tag.substr(0, maxLength || 15).trim(); + var matches = tag.match(/^[.-]*(.+?)[.-]*$/); + if (matches && matches.length > 1) { + tag = matches[1]; + } + return tag; + }, + removePunctuation: function(str) { return str.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`<>'"~()?]/g, ''); }, diff --git a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css index 55f7c09df0..f08e955b17 100644 --- a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css +++ b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css @@ -1,10 +1,14 @@ +/* + * bootstrap-tagsinput v0.8.0 + * + */ + .bootstrap-tagsinput { background-color: #fff; border: 1px solid #ccc; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); display: inline-block; padding: 4px 6px; - margin-bottom: 10px; color: #555; vertical-align: middle; border-radius: 4px; @@ -17,11 +21,21 @@ box-shadow: none; outline: none; background-color: transparent; - padding: 0; + padding: 0 6px; margin: 0; - width: auto !important; + width: auto; max-width: inherit; } +.bootstrap-tagsinput.form-control input::-moz-placeholder { + color: #777; + opacity: 1; +} +.bootstrap-tagsinput.form-control input:-ms-input-placeholder { + color: #777; +} +.bootstrap-tagsinput.form-control input::-webkit-input-placeholder { + color: #777; +} .bootstrap-tagsinput input:focus { border: none; box-shadow: none; @@ -43,4 +57,4 @@ } .bootstrap-tagsinput .tag [data-role="remove"]:hover:active { box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); -} +} \ No newline at end of file diff --git a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js index 4cbe2ae178..49db4041a6 100644 --- a/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js +++ b/public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js @@ -1,6 +1,6 @@ /* - * bootstrap-tagsinput v0.7.1 by Tim Schlechter + * bootstrap-tagsinput v0.8.0 * */ -!function(a){"use strict";function b(b,c){this.isInit=!0,this.itemsArray=[],this.$element=a(b),this.$element.hide(),this.isSelect="SELECT"===b.tagName,this.multiple=this.isSelect&&b.hasAttribute("multiple"),this.objectItems=c&&c.itemValue,this.placeholderText=b.hasAttribute("placeholder")?this.$element.attr("placeholder"):"",this.inputSize=Math.max(1,this.placeholderText.length),this.$container=a('
'),this.$input=a('').appendTo(this.$container),this.$element.before(this.$container),this.build(c),this.isInit=!1}function c(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(a){return a[c]}}}function d(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(){return c}}}function e(a){return a?i.text(a).html():""}function f(a){var b=0;if(document.selection){a.focus();var c=document.selection.createRange();c.moveStart("character",-a.value.length),b=c.text.length}else(a.selectionStart||"0"==a.selectionStart)&&(b=a.selectionStart);return b}function g(b,c){var d=!1;return a.each(c,function(a,c){if("number"==typeof c&&b.which===c)return d=!0,!1;if(b.which===c.which){var e=!c.hasOwnProperty("altKey")||b.altKey===c.altKey,f=!c.hasOwnProperty("shiftKey")||b.shiftKey===c.shiftKey,g=!c.hasOwnProperty("ctrlKey")||b.ctrlKey===c.ctrlKey;if(e&&f&&g)return d=!0,!1}}),d}var h={tagClass:function(a){return"label label-info"},focusClass:"focus",itemValue:function(a){return a?a.toString():a},itemText:function(a){return this.itemValue(a)},itemTitle:function(a){return null},freeInput:!0,addOnBlur:!0,maxTags:void 0,maxChars:void 0,confirmKeys:[13,44],delimiter:",",delimiterRegex:null,cancelConfirmKeysOnEmpty:!1,onTagExists:function(a,b){b.hide().fadeIn()},trimValue:!1,allowDuplicates:!1};b.prototype={constructor:b,add:function(b,c,d){var f=this;if(!(f.options.maxTags&&f.itemsArray.length>=f.options.maxTags)&&(b===!1||b)){if("string"==typeof b&&f.options.trimValue&&(b=a.trim(b)),"object"==typeof b&&!f.objectItems)throw"Can't add objects when itemValue option is not set";if(!b.toString().match(/^\s*$/)){if(f.isSelect&&!f.multiple&&f.itemsArray.length>0&&f.remove(f.itemsArray[0]),"string"==typeof b&&"INPUT"===this.$element[0].tagName){var g=f.options.delimiterRegex?f.options.delimiterRegex:f.options.delimiter,h=b.split(g);if(h.length>1){for(var i=0;if.options.maxInputLength)){var o=a.Event("beforeItemAdd",{item:b,cancel:!1,options:d});if(f.$element.trigger(o),!o.cancel){f.itemsArray.push(b);var p=a(''+e(k)+'');p.data("item",b),f.findInputWrapper().before(p),p.after(" ");var q=a('option[value="'+encodeURIComponent(j)+'"]',f.$element).length||a('option[value="'+e(j)+'"]',f.$element).length;if(f.isSelect&&!q){var r=a("");r.data("item",b),r.attr("value",j),f.$element.append(r)}c||f.pushVal(),(f.options.maxTags===f.itemsArray.length||f.items().toString().length===f.options.maxInputLength)&&f.$container.addClass("bootstrap-tagsinput-max"),a(".typeahead, .twitter-typeahead",f.$container).length&&f.$input.typeahead("val",""),this.isInit?f.$element.trigger(a.Event("itemAddedOnInit",{item:b,options:d})):f.$element.trigger(a.Event("itemAdded",{item:b,options:d}))}}}else if(f.options.onTagExists){var s=a(".tag",f.$container).filter(function(){return a(this).data("item")===n});f.options.onTagExists(b,s)}}}},remove:function(b,c,d){var e=this;if(e.objectItems&&(b="object"==typeof b?a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==e.options.itemValue(b)}):a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==b}),b=b[b.length-1]),b){var f=a.Event("beforeItemRemove",{item:b,cancel:!1,options:d});if(e.$element.trigger(f),f.cancel)return;a(".tag",e.$container).filter(function(){return a(this).data("item")===b}).remove(),a("option",e.$element).filter(function(){return a(this).data("item")===b}).remove(),-1!==a.inArray(b,e.itemsArray)&&e.itemsArray.splice(a.inArray(b,e.itemsArray),1)}c||e.pushVal(),e.options.maxTags>e.itemsArray.length&&e.$container.removeClass("bootstrap-tagsinput-max"),e.$element.trigger(a.Event("itemRemoved",{item:b,options:d}))},removeAll:function(){var b=this;for(a(".tag",b.$container).remove(),a("option",b.$element).remove();b.itemsArray.length>0;)b.itemsArray.pop();b.pushVal()},refresh:function(){var b=this;a(".tag",b.$container).each(function(){var c=a(this),d=c.data("item"),f=b.options.itemValue(d),g=b.options.itemText(d),h=b.options.tagClass(d);if(c.attr("class",null),c.addClass("tag "+e(h)),c.contents().filter(function(){return 3==this.nodeType})[0].nodeValue=e(g),b.isSelect){var i=a("option",b.$element).filter(function(){return a(this).data("item")===d});i.attr("value",f)}})},items:function(){return this.itemsArray},pushVal:function(){var b=this,c=a.map(b.items(),function(a){return b.options.itemValue(a).toString()});b.$element.val(c,!0).trigger("change")},build:function(b){var e=this;if(e.options=a.extend({},h,b),e.objectItems&&(e.options.freeInput=!1),c(e.options,"itemValue"),c(e.options,"itemText"),d(e.options,"tagClass"),e.options.typeahead){var i=e.options.typeahead||{};d(i,"source"),e.$input.typeahead(a.extend({},i,{source:function(b,c){function d(a){for(var b=[],d=0;d$1")}}))}if(e.options.typeaheadjs){var j=null,k={},l=e.options.typeaheadjs;a.isArray(l)?(j=l[0],k=l[1]):k=l,e.$input.typeahead(j,k).on("typeahead:selected",a.proxy(function(a,b){k.valueKey?e.add(b[k.valueKey]):e.add(b),e.$input.typeahead("val","")},e))}e.$container.on("click",a.proxy(function(a){e.$element.attr("disabled")||e.$input.removeAttr("disabled"),e.$input.focus()},e)),e.options.addOnBlur&&e.options.freeInput&&e.$input.on("focusout",a.proxy(function(b){0===a(".typeahead, .twitter-typeahead",e.$container).length&&(e.add(e.$input.val()),e.$input.val(""))},e)),e.$container.on({focusin:function(){e.$container.addClass(e.options.focusClass)},focusout:function(){e.$container.removeClass(e.options.focusClass)}}),e.$container.on("keydown","input",a.proxy(function(b){var c=a(b.target),d=e.findInputWrapper();if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");switch(b.which){case 8:if(0===f(c[0])){var g=d.prev();g.length&&e.remove(g.data("item"))}break;case 46:if(0===f(c[0])){var h=d.next();h.length&&e.remove(h.data("item"))}break;case 37:var i=d.prev();0===c.val().length&&i[0]&&(i.before(d),c.focus());break;case 39:var j=d.next();0===c.val().length&&j[0]&&(j.after(d),c.focus())}var k=c.val().length;Math.ceil(k/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("keypress","input",a.proxy(function(b){var c=a(b.target);if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");var d=c.val(),f=e.options.maxChars&&d.length>=e.options.maxChars;e.options.freeInput&&(g(b,e.options.confirmKeys)||f)&&(0!==d.length&&(e.add(f?d.substr(0,e.options.maxChars):d),c.val("")),e.options.cancelConfirmKeysOnEmpty===!1&&b.preventDefault());var h=c.val().length;Math.ceil(h/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("click","[data-role=remove]",a.proxy(function(b){e.$element.attr("disabled")||e.remove(a(b.target).closest(".tag").data("item"))},e)),e.options.itemValue===h.itemValue&&("INPUT"===e.$element[0].tagName?e.add(e.$element.val()):a("option",e.$element).each(function(){e.add(a(this).attr("value"),!0)}))},destroy:function(){var a=this;a.$container.off("keypress","input"),a.$container.off("click","[role=remove]"),a.$container.remove(),a.$element.removeData("tagsinput"),a.$element.show()},focus:function(){this.$input.focus()},input:function(){return this.$input},findInputWrapper:function(){for(var b=this.$input[0],c=this.$container[0];b&&b.parentNode!==c;)b=b.parentNode;return a(b)}},a.fn.tagsinput=function(c,d,e){var f=[];return this.each(function(){var g=a(this).data("tagsinput");if(g)if(c||d){if(void 0!==g[c]){if(3===g[c].length&&void 0!==e)var h=g[c](d,null,e);else var h=g[c](d);void 0!==h&&f.push(h)}}else f.push(g);else g=new b(this,c),a(this).data("tagsinput",g),f.push(g),"SELECT"===this.tagName&&a("option",a(this)).attr("selected","selected"),a(this).val(a(this).val())}),"string"==typeof c?f.length>1?f:f[0]:f},a.fn.tagsinput.Constructor=b;var i=a("
");a(function(){a("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput()})}(window.jQuery); +!function(a){"use strict";function b(b,c){this.isInit=!0,this.itemsArray=[],this.$element=a(b),this.$element.hide(),this.isSelect="SELECT"===b.tagName,this.multiple=this.isSelect&&b.hasAttribute("multiple"),this.objectItems=c&&c.itemValue,this.placeholderText=b.hasAttribute("placeholder")?this.$element.attr("placeholder"):"",this.inputSize=Math.max(1,this.placeholderText.length),this.$container=a('
'),this.$input=a('').appendTo(this.$container),this.$element.before(this.$container),this.build(c),this.isInit=!1}function c(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(a){return a[c]}}}function d(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(){return c}}}function e(a){return a?i.text(a).html():""}function f(a){var b=0;if(document.selection){a.focus();var c=document.selection.createRange();c.moveStart("character",-a.value.length),b=c.text.length}else(a.selectionStart||"0"==a.selectionStart)&&(b=a.selectionStart);return b}function g(b,c){var d=!1;return a.each(c,function(a,c){if("number"==typeof c&&b.which===c)return d=!0,!1;if(b.which===c.which){var e=!c.hasOwnProperty("altKey")||b.altKey===c.altKey,f=!c.hasOwnProperty("shiftKey")||b.shiftKey===c.shiftKey,g=!c.hasOwnProperty("ctrlKey")||b.ctrlKey===c.ctrlKey;if(e&&f&&g)return d=!0,!1}}),d}var h={tagClass:function(a){return"label label-info"},focusClass:"focus",itemValue:function(a){return a?a.toString():a},itemText:function(a){return this.itemValue(a)},itemTitle:function(a){return null},freeInput:!0,addOnBlur:!0,maxTags:void 0,maxChars:void 0,confirmKeys:[13,44],delimiter:",",delimiterRegex:null,cancelConfirmKeysOnEmpty:!1,onTagExists:function(a,b){b.hide().fadeIn()},trimValue:!1,allowDuplicates:!1,triggerChange:!0};b.prototype={constructor:b,add:function(b,c,d){var f=this;if(!(f.options.maxTags&&f.itemsArray.length>=f.options.maxTags)&&(b===!1||b)){if("string"==typeof b&&f.options.trimValue&&(b=a.trim(b)),"object"==typeof b&&!f.objectItems)throw"Can't add objects when itemValue option is not set";if(!b.toString().match(/^\s*$/)){if(f.isSelect&&!f.multiple&&f.itemsArray.length>0&&f.remove(f.itemsArray[0]),"string"==typeof b&&"INPUT"===this.$element[0].tagName){var g=f.options.delimiterRegex?f.options.delimiterRegex:f.options.delimiter,h=b.split(g);if(h.length>1){for(var i=0;if.options.maxInputLength)){var o=a.Event("beforeItemAdd",{item:b,cancel:!1,options:d});if(f.$element.trigger(o),!o.cancel){f.itemsArray.push(b);var p=a(''+e(k)+'');p.data("item",b),f.findInputWrapper().before(p),p.after(" ");var q=a('option[value="'+encodeURIComponent(j)+'"]',f.$element).length||a('option[value="'+e(j)+'"]',f.$element).length;if(f.isSelect&&!q){var r=a("");r.data("item",b),r.attr("value",j),f.$element.append(r)}c||f.pushVal(f.options.triggerChange),(f.options.maxTags===f.itemsArray.length||f.items().toString().length===f.options.maxInputLength)&&f.$container.addClass("bootstrap-tagsinput-max"),a(".typeahead, .twitter-typeahead",f.$container).length&&f.$input.typeahead("val",""),this.isInit?f.$element.trigger(a.Event("itemAddedOnInit",{item:b,options:d})):f.$element.trigger(a.Event("itemAdded",{item:b,options:d}))}}}else if(f.options.onTagExists){var s=a(".tag",f.$container).filter(function(){return a(this).data("item")===n});f.options.onTagExists(b,s)}}}},remove:function(b,c,d){var e=this;if(e.objectItems&&(b="object"==typeof b?a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==e.options.itemValue(b)}):a.grep(e.itemsArray,function(a){return e.options.itemValue(a)==b}),b=b[b.length-1]),b){var f=a.Event("beforeItemRemove",{item:b,cancel:!1,options:d});if(e.$element.trigger(f),f.cancel)return;a(".tag",e.$container).filter(function(){return a(this).data("item")===b}).remove(),a("option",e.$element).filter(function(){return a(this).data("item")===b}).remove(),-1!==a.inArray(b,e.itemsArray)&&e.itemsArray.splice(a.inArray(b,e.itemsArray),1)}c||e.pushVal(e.options.triggerChange),e.options.maxTags>e.itemsArray.length&&e.$container.removeClass("bootstrap-tagsinput-max"),e.$element.trigger(a.Event("itemRemoved",{item:b,options:d}))},removeAll:function(){var b=this;for(a(".tag",b.$container).remove(),a("option",b.$element).remove();b.itemsArray.length>0;)b.itemsArray.pop();b.pushVal(b.options.triggerChange)},refresh:function(){var b=this;a(".tag",b.$container).each(function(){var c=a(this),d=c.data("item"),f=b.options.itemValue(d),g=b.options.itemText(d),h=b.options.tagClass(d);if(c.attr("class",null),c.addClass("tag "+e(h)),c.contents().filter(function(){return 3==this.nodeType})[0].nodeValue=e(g),b.isSelect){var i=a("option",b.$element).filter(function(){return a(this).data("item")===d});i.attr("value",f)}})},items:function(){return this.itemsArray},pushVal:function(){var b=this,c=a.map(b.items(),function(a){return b.options.itemValue(a).toString()});b.$element.val(c,!0),b.options.triggerChange&&b.$element.trigger("change")},build:function(b){var e=this;if(e.options=a.extend({},h,b),e.objectItems&&(e.options.freeInput=!1),c(e.options,"itemValue"),c(e.options,"itemText"),d(e.options,"tagClass"),e.options.typeahead){var i=e.options.typeahead||{};d(i,"source"),e.$input.typeahead(a.extend({},i,{source:function(b,c){function d(a){for(var b=[],d=0;d$1")}}))}if(e.options.typeaheadjs){var j=null,k={},l=e.options.typeaheadjs;a.isArray(l)?(j=l[0],k=l[1]):k=l,e.$input.typeahead(j,k).on("typeahead:selected",a.proxy(function(a,b){k.valueKey?e.add(b[k.valueKey]):e.add(b),e.$input.typeahead("val","")},e))}e.$container.on("click",a.proxy(function(a){e.$element.attr("disabled")||e.$input.removeAttr("disabled"),e.$input.focus()},e)),e.options.addOnBlur&&e.options.freeInput&&e.$input.on("focusout",a.proxy(function(b){0===a(".typeahead, .twitter-typeahead",e.$container).length&&(e.add(e.$input.val()),e.$input.val(""))},e)),e.$container.on({focusin:function(){e.$container.addClass(e.options.focusClass)},focusout:function(){e.$container.removeClass(e.options.focusClass)}}),e.$container.on("keydown","input",a.proxy(function(b){var c=a(b.target),d=e.findInputWrapper();if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");switch(b.which){case 8:if(0===f(c[0])){var g=d.prev();g.length&&e.remove(g.data("item"))}break;case 46:if(0===f(c[0])){var h=d.next();h.length&&e.remove(h.data("item"))}break;case 37:var i=d.prev();0===c.val().length&&i[0]&&(i.before(d),c.focus());break;case 39:var j=d.next();0===c.val().length&&j[0]&&(j.after(d),c.focus())}var k=c.val().length;Math.ceil(k/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("keypress","input",a.proxy(function(b){var c=a(b.target);if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");var d=c.val(),f=e.options.maxChars&&d.length>=e.options.maxChars;e.options.freeInput&&(g(b,e.options.confirmKeys)||f)&&(0!==d.length&&(e.add(f?d.substr(0,e.options.maxChars):d),c.val("")),e.options.cancelConfirmKeysOnEmpty===!1&&b.preventDefault());var h=c.val().length;Math.ceil(h/5);c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("click","[data-role=remove]",a.proxy(function(b){e.$element.attr("disabled")||e.remove(a(b.target).closest(".tag").data("item"))},e)),e.options.itemValue===h.itemValue&&("INPUT"===e.$element[0].tagName?e.add(e.$element.val()):a("option",e.$element).each(function(){e.add(a(this).attr("value"),!0)}))},destroy:function(){var a=this;a.$container.off("keypress","input"),a.$container.off("click","[role=remove]"),a.$container.remove(),a.$element.removeData("tagsinput"),a.$element.show()},focus:function(){this.$input.focus()},input:function(){return this.$input},findInputWrapper:function(){for(var b=this.$input[0],c=this.$container[0];b&&b.parentNode!==c;)b=b.parentNode;return a(b)}},a.fn.tagsinput=function(c,d,e){var f=[];return this.each(function(){var g=a(this).data("tagsinput");if(g)if(c||d){if(void 0!==g[c]){if(3===g[c].length&&void 0!==e)var h=g[c](d,null,e);else var h=g[c](d);void 0!==h&&f.push(h)}}else f.push(g);else g=new b(this,c),a(this).data("tagsinput",g),f.push(g),"SELECT"===this.tagName&&a("option",a(this)).attr("selected","selected"),a(this).val(a(this).val())}),"string"==typeof c?f.length>1?f:f[0]:f},a.fn.tagsinput.Constructor=b;var i=a("
");a(function(){a("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput()})}(window.jQuery); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 0a35315854..1cb7bb3c77 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -64,12 +64,14 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) { userData.lastonlineISO = utils.toISOString(userData.lastonline || userData.joindate); userData.age = Math.max(0, userData.birthday ? Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000) : 0); + userData.emailClass = 'hide'; + if (!(isAdmin || isGlobalModerator || self || (userData.email && userSettings.showemail))) { userData.email = ''; + } else if (!userSettings.showemail) { + userData.emailClass = ''; } - userData.emailClass = (self && !userSettings.showemail) ? '' : 'hide'; - if (!isAdmin && !isGlobalModerator && !self && !userSettings.showfullname) { userData.fullname = ''; } @@ -172,4 +174,4 @@ function filterLinks(links, self) { }); } -module.exports = helpers; \ No newline at end of file +module.exports = helpers; diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 704dc21a2c..31bd44bd49 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -1,11 +1,12 @@ "use strict"; -var async = require('async'), - - categories = require('../../categories'), - privileges = require('../../privileges'), - analytics = require('../../analytics'), - plugins = require('../../plugins'); +var async = require('async'); + +var categories = require('../../categories'); +var privileges = require('../../privileges'); +var analytics = require('../../analytics'); +var plugins = require('../../plugins'); +var translator = require('../../../public/src/modules/translator') var categoriesController = {}; @@ -24,7 +25,7 @@ categoriesController.get = function(req, res, next) { if (err) { return next(err); } - + data.category.name = translator.escape(data.category.name); res.render('admin/manage/category', { category: data.category, privileges: data.privileges, diff --git a/src/controllers/category.js b/src/controllers/category.js index e3adde496f..f80ad122a7 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -52,7 +52,7 @@ categoryController.get = function(req, res, callback) { } if (!res.locals.isAPI && (!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) { - return helpers.redirect(res, '/category/' + encodeURI(results.categoryData.slug)); + return helpers.redirect(res, '/category/' + results.categoryData.slug); } var settings = results.userSettings; diff --git a/src/controllers/groups.js b/src/controllers/groups.js index a67700c0d9..837ba3b1f6 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -113,7 +113,7 @@ groupsController.members = function(req, res, next) { user.getUsersFromSet('group:' + groupName + ':members', req.uid, 0, 49, next); }, ], function(err, users) { - if (err) { + if (err || !groupName) { return next(err); } diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 94dfe022c2..058a1849b1 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -41,7 +41,7 @@ helpers.redirect = function(res, url) { if (res.locals.isAPI) { res.status(308).json(url); } else { - res.redirect(nconf.get('relative_path') + url); + res.redirect(nconf.get('relative_path') + encodeURI(url)); } }; @@ -105,8 +105,12 @@ helpers.buildTitle = function(pageTitle) { var browserTitle = validator.escape(meta.config.browserTitle || meta.config.title || 'NodeBB'); pageTitle = pageTitle || ''; - var title = titleLayout.replace('{pageTitle}', pageTitle).replace('{browserTitle}', browserTitle); + var title = titleLayout.replace('{pageTitle}', function() { + return pageTitle; + }).replace('{browserTitle}', function() { + return browserTitle; + }); return title; }; -module.exports = helpers; \ No newline at end of file +module.exports = helpers; diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 60f4c1d028..af8f6058c0 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -65,6 +65,9 @@ tagsController.getTags = function(req, res, next) { if (err) { return next(err); } + tags = tags.filter(function(tag) { + return tag && tag.score > 0; + }); var data = { tags: tags, nextStart: 100, diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 27c0ed5b81..c3c2847778 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -55,7 +55,7 @@ topicsController.get = function(req, res, callback) { } if (!res.locals.isAPI && (!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) { - var url = '/topic/' + encodeURI(results.topic.slug); + var url = '/topic/' + results.topic.slug; if (req.params.post_index){ url += '/'+req.params.post_index; } @@ -124,7 +124,7 @@ topicsController.get = function(req, res, callback) { return callback(); } - topics.modifyPostsByPrivilege(topicData.posts, userPrivileges); + topics.modifyPostsByPrivilege(topicData, userPrivileges); plugins.fireHook('filter:controllers.topic.get', {topicData: topicData, uid: req.uid}, next); }, diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 8dff27b47c..59253c5666 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -45,11 +45,26 @@ uploadsController.upload = function(req, res, filesIterator, next) { uploadsController.uploadPost = function(req, res, next) { uploadsController.upload(req, res, function(uploadedFile, next) { - if (uploadedFile.type.match(/image./)) { - uploadImage(req.uid, uploadedFile, next); - } else { - uploadFile(req.uid, uploadedFile, next); + var isImage = uploadedFile.type.match(/image./); + if (isImage && plugins.hasListeners('filter:uploadImage')) { + return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next); } + + async.waterfall([ + function(next) { + if (isImage) { + file.isFileTypeAllowed(uploadedFile.path, next); + } else { + next(); + } + }, + function (next) { + if (parseInt(meta.config.allowFileUploads, 10) !== 1) { + return next(new Error('[[error:uploads-are-disabled]]')); + } + uploadFile(req.uid, uploadedFile, next); + } + ], next); }, next); }; @@ -65,22 +80,27 @@ uploadsController.uploadThumb = function(req, res, next) { return next(err); } - if (uploadedFile.type.match(/image./)) { - var size = parseInt(meta.config.topicThumbSize, 10) || 120; - image.resizeImage({ - path: uploadedFile.path, - extension: path.extname(uploadedFile.name), - width: size, - height: size - }, function(err) { - if (err) { - return next(err); - } - uploadImage(req.uid, uploadedFile, next); - }); - } else { - next(new Error('[[error:invalid-file]]')); + if (!uploadedFile.type.match(/image./)) { + return next(new Error('[[error:invalid-file]]')); } + + var size = parseInt(meta.config.topicThumbSize, 10) || 120; + image.resizeImage({ + path: uploadedFile.path, + extension: path.extname(uploadedFile.name), + width: size, + height: size + }, function(err) { + if (err) { + return next(err); + } + + if (plugins.hasListeners('filter:uploadImage')) { + return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next); + } + + uploadFile(req.uid, uploadedFile, next); + }); }); }, next); }; @@ -102,32 +122,11 @@ uploadsController.uploadGroupCover = function(uid, uploadedFile, callback) { }); }; -function uploadImage(uid, image, callback) { - if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: image, uid: uid}, callback); - } - - file.isFileTypeAllowed(image.path, function(err) { - if (err) { - return callback(err); - } - if (parseInt(meta.config.allowFileUploads, 10)) { - uploadFile(uid, image, callback); - } else { - callback(new Error('[[error:uploads-are-disabled]]')); - } - }); -} - function uploadFile(uid, uploadedFile, callback) { if (plugins.hasListeners('filter:uploadFile')) { return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback); } - if (parseInt(meta.config.allowFileUploads, 10) !== 1) { - return callback(new Error('[[error:uploads-are-disabled]]')); - } - if (!uploadedFile) { return callback(new Error('[[error:invalid-file]]')); } diff --git a/src/controllers/users.js b/src/controllers/users.js index 1eb5989c1c..31ac0bd1cc 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -103,7 +103,7 @@ usersController.getUsers = function(set, uid, page, callback) { } page = parseInt(page, 10) || 1; - var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20; + var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 50; var start = Math.max(0, page - 1) * resultsPerPage; var stop = start + resultsPerPage - 1; diff --git a/src/meta/blacklist.js b/src/meta/blacklist.js index b0269a1f0c..47381e487b 100644 --- a/src/meta/blacklist.js +++ b/src/meta/blacklist.js @@ -78,16 +78,24 @@ Blacklist.validate = function(rules, callback) { var cidr = []; var invalid = []; - var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/; + var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/, + inlineCommentMatch = /#.*$/, + whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1']; // Filter out blank lines and lines starting with the hash character (comments) + // Also trim inputs and remove inline comments rules = rules.map(function(rule) { - rule = rule.trim(); + rule = rule.replace(inlineCommentMatch, '').trim(); return rule.length && !rule.startsWith('#') ? rule : null; }).filter(Boolean); // Filter out invalid rules rules = rules.filter(function(rule) { + if (whitelist.indexOf(rule) !== -1) { + invalid.push(rule); + return false; + } + if (ip.isV4Format(rule)) { ipv4.push(rule); return true; diff --git a/src/meta/templates.js b/src/meta/templates.js index 04d9d31009..88578b235e 100644 --- a/src/meta/templates.js +++ b/src/meta/templates.js @@ -21,10 +21,10 @@ Templates.compile = function(callback) { if (nconf.get('isPrimary') === 'false' || fromFile.match('tpl')) { if (fromFile.match('tpl')) { + emitter.emit('templates:compiled'); winston.info('[minifier] Compiling templates skipped'); } - emitter.emit('templates:compiled'); return callback(); } @@ -48,20 +48,28 @@ function getBaseTemplates(theme) { } function preparePaths(baseTemplatesPaths, callback) { - var coreTemplatesPath = nconf.get('core_templates_path'), - viewsPath = nconf.get('views_dir'); + var coreTemplatesPath = nconf.get('core_templates_path'); + var viewsPath = nconf.get('views_dir'); async.waterfall([ - async.apply(plugins.fireHook, 'static:templates.precompile', {}), - async.apply(plugins.getTemplates) + function (next) { + rimraf(viewsPath, next); + }, + function (next) { + mkdirp(viewsPath, next); + }, + function(viewsPath, next) { + plugins.fireHook('static:templates.precompile', {}, next); + }, + function(next) { + plugins.getTemplates(next); + } ], function(err, pluginTemplates) { if (err) { return callback(err); } winston.verbose('[meta/templates] Compiling templates'); - rimraf.sync(viewsPath); - mkdirp.sync(viewsPath); async.parallel({ coreTpls: function(next) { @@ -111,7 +119,7 @@ function compile(callback) { var themeConfig = require(nconf.get('theme_config')), baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')], viewsPath = nconf.get('views_dir'); - + preparePaths(baseTemplatesPaths, function(err, paths) { if (err) { diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 0248af04f4..d09270c3c9 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -259,7 +259,6 @@ middleware.busyCheck = function(req, res, next) { middleware.applyBlacklist = function(req, res, next) { meta.blacklist.test(req.ip, function(err) { - console.log('blacklist returned:', err); next(err); }); }; diff --git a/src/middleware/render.js b/src/middleware/render.js index 2a9d0ba5d2..79911f71bd 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -13,7 +13,7 @@ module.exports = function(middleware) { req = this.req, defaultFn = function(err, str){ if (err) { - return req.next(err); + return next(err); } self.send(str); @@ -96,4 +96,4 @@ module.exports = function(middleware) { return parts.join(' '); } -}; \ No newline at end of file +}; diff --git a/src/notifications.js b/src/notifications.js index af88fd7b5b..498337a7db 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -106,7 +106,7 @@ var async = require('async'), }); callback(null, _nids.filter(function(nid, idx) { - return mergeIds.indexOf(sets[idx]) !== -1 + return mergeIds.indexOf(sets[idx]) !== -1; })); }); }; @@ -450,10 +450,14 @@ var async = require('async'), }); var numUsers = usernames.length; + var title = S(notifications[modifyIndex].topicTitle || '').decodeHTMLEntities().s; + var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + titleEscaped = titleEscaped ? (', ' + titleEscaped) : ''; + if (numUsers === 2) { - notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + ', ' + notifications[modifyIndex].topicTitle + ']]'; + notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + titleEscaped + ']]'; } else if (numUsers > 2) { - notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers-1) + ', ' + notifications[modifyIndex].topicTitle + ']]'; + notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]'; } break; diff --git a/src/posts/edit.js b/src/posts/edit.js index d4c3a3c732..fece8aa640 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -118,7 +118,7 @@ module.exports = function(Posts) { if (title) { topicData.title = title; - topicData.slug = tid + '/' + utils.slugify(title); + topicData.slug = tid + '/' + (utils.slugify(title) || 'topic'); } if (data.topic_thumb) { diff --git a/src/posts/user.js b/src/posts/user.js index 0d41397960..cd675cbfa3 100644 --- a/src/posts/user.js +++ b/src/posts/user.js @@ -1,6 +1,7 @@ 'use strict'; var async = require('async'), + validator = require('validator'), db = require('../database'), user = require('../user'), @@ -69,6 +70,8 @@ module.exports = function(Posts) { userData.picture = userData.picture || ''; userData.status = user.getStatus(userData); userData.groupTitle = results.groupTitles[i].groupTitle; + userData.signature = validator.escape(userData.signature || ''); + userData.fullname = validator.escape(userData.fullname || ''); }); async.map(userData, function(userData, next) { diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 6cc66483e1..83e635fde9 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -37,7 +37,7 @@ module.exports = function(privileges) { var disabled = parseInt(results.disabled, 10) === 1; var locked = parseInt(topic.locked, 10) === 1; - var isAdminOrMod = results.isAdministrator || results.isModerator; + var isAdminOrMod = results.isAdministrator || results.isModerator; var editable = isAdminOrMod; var deletable = isAdminOrMod || results.isOwner; diff --git a/src/routes/feeds.js b/src/routes/feeds.js index b71fc99aab..bfa2945fb5 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -49,7 +49,7 @@ function generateForTopic(req, res, callback) { return callback(err); } - topics.modifyPostsByPrivilege(topicData.posts, userPrivileges); + topics.modifyPostsByPrivilege(topicData, userPrivileges); var description = topicData.posts.length ? topicData.posts[0].content : ''; var image_url = topicData.posts.length ? topicData.posts[0].picture : ''; diff --git a/src/social.js b/src/social.js index c450421dcb..fec8fb036c 100644 --- a/src/social.js +++ b/src/social.js @@ -6,7 +6,13 @@ var async = require('async'); var social = {}; +social.postSharing = null; + social.getPostSharing = function(callback) { + if (social.postSharing) { + return callback(null, social.postSharing); + } + var networks = [ { id: "facebook", @@ -39,20 +45,41 @@ social.getPostSharing = function(callback) { networks[i].activated = (activated.indexOf(network.id) !== -1); }); + social.postSharing = networks; next(null, networks); }); } ], callback); }; -social.setActivePostSharingNetworks = function(networkIDs, callback) { - db.delete('social:posts.activated', function(err) { - if (!networkIDs.length) { +social.getActivePostSharing = function(callback) { + social.getPostSharing(function(err, networks) { + if (err) { return callback(err); } - - db.setAdd('social:posts.activated', networkIDs, callback); + networks = networks.filter(function(network) { + return network && network.activated; + }); + callback(null, networks); }); }; +social.setActivePostSharingNetworks = function(networkIDs, callback) { + async.waterfall([ + function (next) { + db.delete('social:posts.activated', next); + }, + function (next) { + if (!networkIDs.length) { + return next(); + } + db.setAdd('social:posts.activated', networkIDs, next); + }, + function (next) { + social.postSharing = null; + next(); + } + ], callback); +}; + module.exports = social; \ No newline at end of file diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 8e35d74bca..a54db76b05 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -155,7 +155,7 @@ 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) { if (err) { return callback(err); @@ -178,16 +178,16 @@ SocketGroups.create = function(socket, data, callback) { }; SocketGroups.delete = function(socket, data, callback) { - if (data.groupName === 'administrators' || data.groupName === 'registered-users') { + if (data.groupName === 'administrators' || + data.groupName === 'registered-users' || + data.groupName === 'Global Moderators') { return callback(new Error('[[error:not-allowed]]')); } - var tasks = { + async.parallel({ isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName), isAdmin: async.apply(user.isAdministrator, socket.uid) - }; - - async.parallel(tasks, function(err, checks) { + }, function(err, checks) { if (err) { return callback(err); } diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index 3aeaaf0bf3..5524faf156 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -2,6 +2,7 @@ var async = require('async'); var winston = require('winston'); +var S = require('string'); var nconf = require('nconf'); var websockets = require('./index'); @@ -62,8 +63,11 @@ SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification) return; } + var title = S(results.topicTitle).decodeHTMLEntities().s; + var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicTitle + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]', bodyLong: results.postObj.content, pid: pid, nid: 'post:' + pid + ':uid:' + fromuid, @@ -93,8 +97,11 @@ SocketHelpers.sendNotificationToTopicOwner = function(tid, fromuid, notification return; } + var title = S(results.topicData.title).decodeHTMLEntities().s; + var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + notifications.create({ - bodyShort: '[[' + notification + ', ' + results.username + ', ' + results.topicData.title + ']]', + bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]', path: nconf.get('relative_path') + '/topic/' + results.topicData.slug, nid: 'tid:' + tid + ':uid:' + fromuid, from: fromuid @@ -111,4 +118,4 @@ SocketHelpers.emitToTopicAndCategory = function(event, data) { websockets.in('category_' + data.cid).emit(event, data); }; -module.exports = SocketHelpers; \ No newline at end of file +module.exports = SocketHelpers; diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 16d48b5deb..ebd27730e4 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -240,6 +240,7 @@ SocketModules.chats.markRead = function(socket, roomId, callback) { user.notifications.pushCount(socket.uid); }); + server.in('uid_' + socket.uid).emit('event:chats.markedAsRead', {roomId: roomId}); callback(); }); }; diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js index eaefcea1e2..bbf7a4721c 100644 --- a/src/socket.io/posts/flag.js +++ b/src/socket.io/posts/flag.js @@ -1,6 +1,7 @@ 'use strict'; var async = require('async'); +var S = require('string'); var user = require('../../user'); var groups = require('../../groups'); @@ -82,8 +83,11 @@ module.exports = function(SocketPosts) { }, next); }, function (results, next) { + var title = S(post.topic.title).decodeHTMLEntities().s; + var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ','); + notifications.create({ - bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + post.topic.title + ']]', + bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + titleEscaped + ']]', bodyLong: post.content, pid: data.pid, nid: 'post_flag:' + data.pid + ':uid:' + socket.uid, @@ -163,4 +167,4 @@ module.exports = function(SocketPosts) { }, ], callback); }; -}; \ No newline at end of file +}; diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index c3f08461fe..5c59b28918 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -32,7 +32,7 @@ module.exports = function(SocketPosts) { plugins.fireHook('filter:post.tools', {pid: data.pid, uid: socket.uid, tools: []}, next); }, postSharing: function(next) { - social.getPostSharing(next); + social.getActivePostSharing(next); } }, function(err, results) { if (err) { diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 3ffe1c65c8..5c45c6e4fb 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -6,6 +6,7 @@ var topics = require('../../topics'); var privileges = require('../../privileges'); var meta = require('../../meta'); var utils = require('../../../public/src/utils'); +var social = require('../../social'); module.exports = function(SocketTopics) { @@ -68,6 +69,9 @@ module.exports = function(SocketTopics) { }, posts: function(next) { topics.getTopicPosts(data.tid, set, start, stop, socket.uid, reverse, next); + }, + postSharing: function (next) { + social.getActivePostSharing(next); } }, function(err, topicData) { if (err) { @@ -81,7 +85,7 @@ module.exports = function(SocketTopics) { topicData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; topicData['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; - topics.modifyPostsByPrivilege(topicData.posts, results.privileges); + topics.modifyPostsByPrivilege(topicData, results.privileges); callback(null, topicData); }); }); diff --git a/src/socket.io/topics/tags.js b/src/socket.io/topics/tags.js index 535fdc3027..f55ec377fb 100644 --- a/src/socket.io/topics/tags.js +++ b/src/socket.io/topics/tags.js @@ -20,14 +20,16 @@ module.exports = function(SocketTopics) { return callback(new Error('[[error:invalid-data]]')); } - var start = parseInt(data.after, 10), - stop = start + 99; + var start = parseInt(data.after, 10); + var stop = start + 99; topics.getTags(start, stop, function(err, tags) { if (err) { return callback(err); } - + tags = tags.filter(function(tag) { + return tag && tag.score > 0; + }); callback(null, {tags: tags, nextStart: stop + 1}); }); }; diff --git a/src/topics.js b/src/topics.js index 4f7f5a066d..385d3ae558 100644 --- a/src/topics.js +++ b/src/topics.js @@ -1,15 +1,16 @@ "use strict"; -var async = require('async'), - _ = require('underscore'), +var async = require('async'); +var _ = require('underscore'); - db = require('./database'), - posts = require('./posts'), - utils = require('../public/src/utils'), - plugins = require('./plugins'), - user = require('./user'), - categories = require('./categories'), - privileges = require('./privileges'); +var db = require('./database'); +var posts = require('./posts'); +var utils = require('../public/src/utils'); +var plugins = require('./plugins'); +var user = require('./user'); +var categories = require('./categories'); +var privileges = require('./privileges'); +var social = require('./social'); (function(Topics) { @@ -125,11 +126,14 @@ var async = require('async'), user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next); }, categories: function(next) { - categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next); + categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'image', 'bgColor', 'color', 'disabled'], next); }, hasRead: function(next) { Topics.hasReadTopics(tids, uid, next); }, + bookmarks: function(next) { + Topics.getUserBookmarks(tids, uid, next); + }, teasers: function(next) { Topics.getTeasers(topics, next); }, @@ -154,6 +158,7 @@ var async = require('async'), topics[i].locked = parseInt(topics[i].locked, 10) === 1; topics[i].deleted = parseInt(topics[i].deleted, 10) === 1; topics[i].unread = !results.hasRead[i]; + topics[i].bookmark = results.bookmarks[i]; topics[i].unreplied = !topics[i].teaser; } } @@ -179,7 +184,8 @@ var async = require('async'), 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), - bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid) + bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid), + postSharing: async.apply(social.getActivePostSharing) }, next); }, function (results, next) { @@ -189,6 +195,7 @@ var async = require('async'), topicData.tags = results.tags; topicData.isFollowing = results.isFollowing[0]; topicData.bookmark = results.bookmark; + topicData.postSharing = results.postSharing; topicData.unreplied = parseInt(topicData.postcount, 10) === 1; topicData.deleted = parseInt(topicData.deleted, 10) === 1; @@ -289,6 +296,17 @@ var async = require('async'), db.sortedSetScore('tid:' + tid + ':bookmarks', uid, callback); }; + Topics.getUserBookmarks = function(tids, uid, callback) { + if (!parseInt(uid, 10)) { + return callback(null, tids.map(function() { + return null; + })); + } + db.sortedSetsScore(tids.map(function(tid) { + return 'tid:' + tid + ':bookmarks'; + }), uid, callback); + }; + Topics.setUserBookmark = function(tid, uid, index, callback) { db.sortedSetAdd('tid:' + tid + ':bookmarks', index, uid, callback); }; diff --git a/src/topics/data.js b/src/topics/data.js index a32ce5256a..9d153b5b78 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -59,7 +59,7 @@ module.exports = function(Topics) { return; } topic.titleRaw = topic.title; - topic.title = validator.escape(topic.title); + topic.title = validator.escape(String(topic.title)); topic.timestampISO = utils.toISOString(topic.timestamp); topic.lastposttimeISO = utils.toISOString(topic.lastposttime); } diff --git a/src/topics/fork.js b/src/topics/fork.js index fa4633fdd6..948cb3207e 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -1,14 +1,14 @@ 'use strict'; -var async = require('async'), - winston = require('winston'), - - db = require('../database'), - user = require('../user'), - posts = require('../posts'), - privileges = require('../privileges'), - plugins = require('../plugins'); +var async = require('async'); +var winston = require('winston'); +var db = require('../database'); +var user = require('../user'); +var posts = require('../posts'); +var privileges = require('../privileges'); +var plugins = require('../plugins'); +var meta = require('../meta'); module.exports = function(Topics) { @@ -18,8 +18,10 @@ module.exports = function(Topics) { title = title.trim(); } - if (!title) { - return callback(new Error('[[error:invalid-title]]')); + if (title.length < parseInt(meta.config.minimumTitleLength, 10)) { + return callback(new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]')); + } else if (title.length > parseInt(meta.config.maximumTitleLength, 10)) { + return callback(new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]')); } if (!pids || !pids.length) { diff --git a/src/topics/posts.js b/src/topics/posts.js index 8177e93ebd..445eab4637 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -1,16 +1,15 @@ - 'use strict'; -var async = require('async'), - _ = require('underscore'), - validator = require('validator'), +var async = require('async'); +var _ = require('underscore'); +var validator = require('validator'); - db = require('../database'), - user = require('../user'), - favourites = require('../favourites'), - posts = require('../posts'), - meta = require('../meta'); +var db = require('../database'); +var user = require('../user'); +var favourites = require('../favourites'); +var posts = require('../posts'); +var meta = require('../meta'); module.exports = function(Topics) { @@ -138,16 +137,20 @@ module.exports = function(Topics) { }); }; - Topics.modifyPostsByPrivilege = function(postData, topicPrivileges) { - postData.forEach(function(post) { + Topics.modifyPostsByPrivilege = function(topicData, topicPrivileges) { + var loggedIn = !!parseInt(topicPrivileges.uid, 10); + topicData.posts.forEach(function(post) { if (post) { post.display_moderator_tools = topicPrivileges.isAdminOrMod || post.selfPost; post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0; - post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || !post.deleted; + post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || ((loggedIn || topicData.postSharing.length) && !post.deleted); post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined; if (post.deleted && !(topicPrivileges.isAdminOrMod || post.selfPost)) { post.content = '[[topic:post_is_deleted]]'; + if (post.user) { + post.user.signature = ''; + } } } }); diff --git a/src/topics/tags.js b/src/topics/tags.js index 1b47a5d0e6..83702331b7 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -1,12 +1,13 @@ 'use strict'; -var async = require('async'), +var async = require('async'); - db = require('../database'), - meta = require('../meta'), - _ = require('underscore'), - plugins = require('../plugins'); +var db = require('../database'); +var meta = require('../meta'); +var _ = require('underscore'); +var plugins = require('../plugins'); +var utils = require('../../public/src/utils'); module.exports = function(Topics) { @@ -24,7 +25,9 @@ module.exports = function(Topics) { }, function (data, next) { tags = data.tags.slice(0, meta.config.maximumTagsPerTopic || 5); - tags = tags.map(Topics.cleanUpTag).filter(function(tag, index, array) { + tags = tags.map(function(tag) { + return utils.cleanUpTag(tag, meta.config.maximumTagLength); + }).filter(function(tag, index, array) { return tag && tag.length >= (meta.config.minimumTagLength || 3) && array.indexOf(tag) === index; }); @@ -45,20 +48,6 @@ module.exports = function(Topics) { ], callback); }; - Topics.cleanUpTag = function(tag) { - if (typeof tag !== 'string' || !tag.length ) { - return ''; - } - tag = tag.trim().toLowerCase(); - tag = tag.replace(/[,\/#!$%\^\*;:{}=_`<>'"~()?\|]/g, ''); - tag = tag.substr(0, meta.config.maximumTagLength || 15).trim(); - var matches = tag.match(/^[.-]*(.+?)[.-]*$/); - if (matches && matches.length > 1) { - tag = matches[1]; - } - return tag; - }; - Topics.updateTag = function(tag, data, callback) { db.setObject('tag:' + tag, data, callback); }; diff --git a/src/topics/teaser.js b/src/topics/teaser.js index c772b271d4..5d730de74d 100644 --- a/src/topics/teaser.js +++ b/src/topics/teaser.js @@ -74,7 +74,7 @@ module.exports = function(Topics) { tidToPost[topic.tid].index = meta.config.teaserPost === 'first' ? 1 : counts[index]; if (tidToPost[topic.tid].content) { var s = S(tidToPost[topic.tid].content); - tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags).s; + tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags.concat(['img'])).s; } } return tidToPost[topic.tid]; diff --git a/src/user/create.js b/src/user/create.js index 1d1543e863..ae69f1ad5d 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -1,14 +1,13 @@ 'use strict'; -var async = require('async'), - db = require('../database'), - utils = require('../../public/src/utils'), - validator = require('validator'), - plugins = require('../plugins'), - groups = require('../groups'), - meta = require('../meta'), - notifications = require('../notifications'), - translator = require('../../public/src/modules/translator'); +var async = require('async'); +var db = require('../database'); +var utils = require('../../public/src/utils'); +var validator = require('validator'); +var plugins = require('../plugins'); +var groups = require('../groups'); +var meta = require('../meta'); + module.exports = function(User) { @@ -90,7 +89,11 @@ module.exports = function(User) { db.sortedSetAdd('userslug:uid', userData.uid, userData.userslug, next); }, function(next) { - db.sortedSetsAdd(['users:joindate', 'users:online', 'users:notvalidated'], timestamp, userData.uid, next); + var sets = ['users:joindate', 'users:online']; + if (parseInt(userData.uid) !== 1) { + sets.push('users:notvalidated'); + } + db.sortedSetsAdd(sets, timestamp, userData.uid, next); }, function(next) { db.sortedSetsAdd(['users:postcount', 'users:reputation'], 0, userData.uid, next); @@ -176,7 +179,7 @@ module.exports = function(User) { next(); } } - }, function(err, results) { + }, function(err) { callback(err); }); }; diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl index 116a0fd0f5..c36f08a693 100644 --- a/src/views/admin/general/navigation.tpl +++ b/src/views/admin/general/navigation.tpl @@ -84,6 +84,15 @@
+ Installed Plugins Required: + +
+ +
+ diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl index f579065df5..a50fdba51b 100644 --- a/src/views/admin/manage/group.tpl +++ b/src/views/admin/manage/group.tpl @@ -63,7 +63,7 @@