From 86317a4b7995b13d4c113b8ab7d8ecdee9e4b3a2 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Fri, 17 Apr 2015 14:35:45 +0200 Subject: [PATCH 001/317] Replace schemeless URLs for emails --- src/topics/follow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/follow.js b/src/topics/follow.js index 082a49f024..cf63b101e0 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -157,7 +157,7 @@ module.exports = function(Topics) { pid: postData.pid, subject: '[' + (meta.config.title || 'NodeBB') + '] ' + title, intro: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + title + ']]', - postBody: postData.content, + postBody: postData.content.replace(/"\/\//g, '"http://'), site_title: meta.config.title || 'NodeBB', username: data.userData.username, userslug: data.userData.userslug, From ad7b561dd4208b375d0face3f4ce67d1d7e66ca2 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Sun, 3 May 2015 23:36:23 -0600 Subject: [PATCH 002/317] Fixed translator backwards compatibility issue Also removed the _clearMenus global object because populating the global namespace is bad, bad, bad --- public/src/modules/translator.js | 6 +++-- public/src/overrides.js | 42 +++++++++++++++++--------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index ac3be2728e..df8ac5d5ef 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -295,11 +295,13 @@ if (typeof define === 'function' && define.amd) { define('translator', translator); + var _translator = translator; + // Expose a global `translator` object for backwards compatibility window.translator = { translate: function() { console.warn('[translator] Global invocation of the translator is now deprecated, please `require` the module instead.'); - translator.translate.apply(translator, arguments); + _translator.translate.apply(_translator, arguments); } } } @@ -307,4 +309,4 @@ typeof exports === 'object' ? exports : typeof define === 'function' && define.amd ? {} : translator = {} -); \ No newline at end of file +); diff --git a/public/src/overrides.js b/public/src/overrides.js index 8c0e492cce..dde1b55dd3 100644 --- a/public/src/overrides.js +++ b/public/src/overrides.js @@ -79,25 +79,27 @@ if ('undefined' !== typeof window) { })(jQuery || {fn:{}}); - - // FIX FOR #1245 - https://github.com/NodeBB/NodeBB/issues/1245 - // from http://stackoverflow.com/questions/15931962/bootstrap-dropdown-disappear-with-right-click-on-firefox - // obtain a reference to the original handler - var _clearMenus = $._data(document, "events").click.filter(function (el) { - return el.namespace === 'bs.data-api.dropdown' && el.selector === undefined; - }); - - if(_clearMenus.length) { - _clearMenus = _clearMenus[0].handler; - } - - // disable the old listener - $(document) - .off('click.data-api.dropdown', _clearMenus) - .on('click.data-api.dropdown', function (e) { - // call the handler only when not right-click - if (e.button !== 2) { - _clearMenus(); - } + (function(){ + // FIX FOR #1245 - https://github.com/NodeBB/NodeBB/issues/1245 + // from http://stackoverflow.com/questions/15931962/bootstrap-dropdown-disappear-with-right-click-on-firefox + // obtain a reference to the original handler + var _clearMenus = $._data(document, "events").click.filter(function (el) { + return el.namespace === 'bs.data-api.dropdown' && el.selector === undefined; }); + + if(_clearMenus.length) { + _clearMenus = _clearMenus[0].handler; + } + + // disable the old listener + $(document) + .off('click.data-api.dropdown', _clearMenus) + .on('click.data-api.dropdown', function (e) { + // call the handler only when not right-click + if (e.button !== 2) { + _clearMenus(); + } + }); + })(); + } From 7c5ba9b7b11a80156c6593837cb38cc447196b46 Mon Sep 17 00:00:00 2001 From: Andrea Cardinale Date: Tue, 12 May 2015 23:52:48 +0200 Subject: [PATCH 003/317] Revert "Add hooks: action:post.favourite and action:post.unfavourite" This reverts commit 1d22a2d46b73c39770c6e9927aa31ab5a1d91937. --- src/favourites.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/favourites.js b/src/favourites.js index 58a945e211..e38e11298e 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -307,12 +307,6 @@ var async = require('async'), results.postData.reputation = count; posts.setPostField(pid, 'reputation', count, next); }, - function(next) { - plugins.fireHook('action:post.' + type, { - pid: pid, - uid: uid, - }, next); - }, function(next) { next(null, { post: results.postData, From 297b5906edf819eda7a23612498e82ea9ae7de4b Mon Sep 17 00:00:00 2001 From: pentode Date: Tue, 19 May 2015 14:55:06 -0400 Subject: [PATCH 004/317] add feature to define mongo client connect options via config.json --- package.json | 1 + src/database/mongo.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/package.json b/package.json index 278141c507..dabfe11799 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "templates.js": "^0.2.3", "uglify-js": "git+https://github.com/julianlam/UglifyJS2.git", "underscore": "~1.8.3", + "underscore.deep": "^0.5.1", "validator": "^3.30.0", "winston": "^0.9.0", "xregexp": "~2.0.0" diff --git a/src/database/mongo.js b/src/database/mongo.js index cc9bacec1c..9b0cd64842 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -7,8 +7,11 @@ async = require('async'), nconf = require('nconf'), session = require('express-session'), + _ = require('underscore'), db, mongoClient; + _.mixin(require('underscore.deep')); + module.questions = [ { name: 'mongo:host', @@ -88,6 +91,9 @@ poolSize: parseInt(nconf.get('mongo:poolSize'), 10) || 10 } }; + + connOptions = _.deepExtend((nconf.get('mongo:options') || {}), connOptions); + mongoClient.connect(connString, connOptions, function(err, _db) { if (err) { winston.error("NodeBB could not connect to your Mongo database. Mongo returned the following error: " + err.message); From 8414e31730486743846087941534e7099d9da9bc Mon Sep 17 00:00:00 2001 From: Timothy Fike Date: Wed, 20 May 2015 21:41:36 -0400 Subject: [PATCH 005/317] Fix specificity on panel heading. Needs to be specific so I can put an accordion inside. :sunglasses: --- public/src/admin/extend/widgets.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/src/admin/extend/widgets.js b/public/src/admin/extend/widgets.js index b57453ffab..4e8d3160aa 100644 --- a/public/src/admin/extend/widgets.js +++ b/public/src/admin/extend/widgets.js @@ -58,9 +58,9 @@ define('admin/extend/widgets', function() { panel.remove(); } }); - }).on('mouseup', '.panel-heading', function(evt) { - if ( !( $(this).parents('.widget-panel').is('.ui-sortable-helper') || $(evt.target).closest('.delete-widget').length ) ) { - $(this).parents('.widget-panel').children('.panel-body').toggleClass('hidden'); + }).on('mouseup', '> .panel > .panel-heading', function(evt) { + if ( !( $(this).parent().is('.ui-sortable-helper') || $(evt.target).closest('.delete-widget').length ) ) { + $(this).parent().children('.panel-body').toggleClass('hidden'); } }); From 09ee1ae77ef32a1957ecaef541c0d060f37032ec Mon Sep 17 00:00:00 2001 From: Timothy Fike Date: Thu, 21 May 2015 14:37:23 -0400 Subject: [PATCH 006/317] Call Plugins.addLanguages on reload. Fixes #3153 Ensures routes are set correctly for custom languages. --- src/plugins.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins.js b/src/plugins.js index 156ba49a3b..078745742c 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -67,11 +67,6 @@ var fs = require('fs'), emitter.emit('plugins:loaded'); callback(); }); - - Plugins.registerHook('core', { - hook: 'static:app.load', - method: addLanguages - }); }; Plugins.reload = function(callback) { @@ -84,6 +79,11 @@ var fs = require('fs'), Plugins.clientScripts.length = 0; Plugins.libraryPaths.length = 0; + Plugins.registerHook('core', { + hook: 'static:app.load', + method: addLanguages + }); + async.waterfall([ function(next) { db.getSortedSetRange('plugins:active', 0, -1, next); From 434f1d924eb52cb27b373fb02a3271274ad88e35 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 23 May 2015 19:01:09 -0400 Subject: [PATCH 007/317] bumped the package's version number for dev purposes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ca0456426..27d4933f94 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPLv3 or later", "description": "NodeBB Forum", - "version": "0.7.0-dev", + "version": "0.7.1-dev", "homepage": "http://www.nodebb.org", "repository": { "type": "git", From 3055ee96a1f336ff4c4989eb86bde042b730036e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 23 May 2015 19:12:13 -0400 Subject: [PATCH 008/317] latest translations and fallbacks --- public/language/ar/category.json | 4 +-- public/language/ar/error.json | 2 +- public/language/ar/login.json | 2 +- public/language/ar/register.json | 10 +++--- public/language/ar/reset_password.json | 12 +++---- public/language/ar/tags.json | 8 ++--- public/language/bg/error.json | 4 +-- public/language/es/error.json | 32 +++++++++--------- public/language/es/register.json | 4 +-- public/language/es/topic.json | 2 +- public/language/es/user.json | 14 ++++---- public/language/et/email.json | 46 +++++++++++++------------- public/language/fr/error.json | 4 +-- public/language/ru/user.json | 2 +- public/language/sv/category.json | 2 +- public/language/sv/email.json | 10 +++--- public/language/sv/login.json | 4 +-- public/language/sv/search.json | 2 +- public/language/sv/user.json | 32 +++++++++--------- 19 files changed, 98 insertions(+), 98 deletions(-) diff --git a/public/language/ar/category.json b/public/language/ar/category.json index 6a975425c0..b1732ff8ba 100644 --- a/public/language/ar/category.json +++ b/public/language/ar/category.json @@ -1,11 +1,11 @@ { "new_topic_button": "موضوع جديد", - "guest-login-post": "المرجو تسجيل الدخول أوَّلا", + "guest-login-post": "يجب عليك تسجيل الدخول للرد", "no_topics": "لا توجد مواضيع في هذه الفئةلم لا تحاول إنشاء موضوع؟
", "browsing": "تصفح", "no_replies": "لم يرد أحد", "share_this_category": "انشر هذه الفئة", - "watch": "Watch", + "watch": "متابعة", "ignore": "تجاهل", "watch.message": "You are now watching updates from this category", "ignore.message": "You are now ignoring updates from this category" diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 4c10a8cf38..04ee524176 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -1,7 +1,7 @@ { "invalid-data": "بيانات غير صالحة", "not-logged-in": "لم تقم بتسجيل الدخول", - "account-locked": "تم إقفال حسابكم مؤقتًا.", + "account-locked": "تم حظر حسابك مؤقتًا.", "search-requires-login": "Searching requires an account - please login or register.", "invalid-cid": "قائمة غير موجودة", "invalid-tid": "موضوع غير متواجد", diff --git a/public/language/ar/login.json b/public/language/ar/login.json index 878a32533c..29d256203f 100644 --- a/public/language/ar/login.json +++ b/public/language/ar/login.json @@ -7,5 +7,5 @@ "alternative_logins": "تسجيلات الدخول البديلة", "failed_login_attempt": "فشلت محاولة تسجيل الدخول، يرجى المحاولة مرة أخرى.", "login_successful": "قمت بتسجيل الدخول بنجاح!", - "dont_have_account": "لم تفتح حسابك بعد؟" + "dont_have_account": "لا تملك حساب؟" } \ No newline at end of file diff --git a/public/language/ar/register.json b/public/language/ar/register.json index d456c82572..311ced9103 100644 --- a/public/language/ar/register.json +++ b/public/language/ar/register.json @@ -2,15 +2,15 @@ "register": "تسجيل", "help.email": "افتراضيا، سيتم إخفاء بريدك الإلكتروني من الجمهور.", "help.username_restrictions": "اسم مستخدم فريدة من نوعها بين1% و2% حرفا. يمكن للآخرين ذكرك @ <'span id='your-username> اسم المستخدم .", - "help.minimum_password_length": "كلمتك السر يجب أن تكون على الأقل متألفة من 1% أحرف", + "help.minimum_password_length": "كلمة المرور يجب أن تكون على الأقل بها 1% أحرف", "email_address": "عنوان البريد الإلكتروني", "email_address_placeholder": "ادخل عنوان البريد الإلكتروني", "username": "اسم المستخدم", "username_placeholder": "أدخل اسم المستخدم", - "password": "كلمة السر", - "password_placeholder": "أدخل كلمة السر", - "confirm_password": "تأكيد كلمة السر", - "confirm_password_placeholder": "تأكيد كلمة السر", + "password": "كلمة المرور", + "password_placeholder": "أدخل كلمة المرور", + "confirm_password": "تأكيد كلمة المرور", + "confirm_password_placeholder": "تأكيد كلمة المرور", "register_now_button": "قم بالتسجيل الآن", "alternative_registration": "طريقة تسجيل بديلة", "terms_of_use": "شروط الاستخدام", diff --git a/public/language/ar/reset_password.json b/public/language/ar/reset_password.json index cdd2b378a0..f6107ec45b 100644 --- a/public/language/ar/reset_password.json +++ b/public/language/ar/reset_password.json @@ -1,12 +1,12 @@ { - "reset_password": "إعادة تعيين كلمة السر", - "update_password": "تحديث كلمة السر", - "password_changed.title": "تم تغير كلمة السر", - "password_changed.message": "

تم تغير كلمة السر بنجاح. يرجى إعادة الدخول

", + "reset_password": "إعادة تعيين كلمة المرور", + "update_password": "تحديث كلمة المرور", + "password_changed.title": "تم تغير كلمة المرور", + "password_changed.message": "

تم تغير كلمة المرور بنجاح، الرجاء إعادة الدخول

", "wrong_reset_code.title": "رمز إعادة التعيين غير صحيح", "wrong_reset_code.message": "رمز إعادة التعين غير صحيح، يرجى المحاولة مرة أخرى أو اطلب رمزا جديدا", - "new_password": "كلمة السر الجديدة", - "repeat_password": "تأكيد كلمة السر", + "new_password": "كلمة المرور الجديدة", + "repeat_password": "تأكيد كلمة المرور", "enter_email": "يرجى إدخال عنوان البريد الإلكتروني الخاص بك وسوف نرسل لك رسالة بالبريد الالكتروني مع تعليمات حول كيفية إستعادة حسابك.", "enter_email_address": "ادخل عنوان البريد الإلكتروني", "password_reset_sent": "إعادة تعيين كلمة السر أرسلت", diff --git a/public/language/ar/tags.json b/public/language/ar/tags.json index f2eccbd1c0..2798a0c8c8 100644 --- a/public/language/ar/tags.json +++ b/public/language/ar/tags.json @@ -1,7 +1,7 @@ { - "no_tag_topics": "لاوجود لمواضيع تحمل هذا الوسم.", - "tags": "بطاقات", + "no_tag_topics": "لا يوجد مواضيع بهذه الكلمة الدلالية.", + "tags": "الكلمات الدلالية", "enter_tags_here": "Enter tags here, between %1 and %2 characters each.", - "enter_tags_here_short": "أدخل البطاقات...", - "no_tags": "لاتوجد هناك بطاقات بعد." + "enter_tags_here_short": "أدخل الكلمات الدلالية...", + "no_tags": "لا يوجد كلمات دلالية بعد." } \ No newline at end of file diff --git a/public/language/bg/error.json b/public/language/bg/error.json index ce39acb82d..a48531a6ea 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -67,8 +67,8 @@ "topic-thumbnails-are-disabled": "Иконките на темите са изключени.", "invalid-file": "Грешен файл", "uploads-are-disabled": "Качването не е разрешено", - "signature-too-long": "Sorry, your signature cannot be longer than %1 character(s).", - "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", + "signature-too-long": "Съжаляваме, но подписът Ви трябва да съдържа не повече от %1 символ(а).", + "about-me-too-long": "Съжаляваме, но информацията за Вас трябва да съдържа не повече от %1 символ(а).", "cant-chat-with-yourself": "Не можете да пишете чат съобщение на себе си!", "chat-restricted": "Този потребител е ограничил чат съобщенията до себе си. Той трябва първо да Ви последва, преди да можете да си пишете с него.", "too-many-messages": "Изпратили сте твърде много съобщения. Моля, изчакайте малко.", diff --git a/public/language/es/error.json b/public/language/es/error.json index cd90a8470b..02b0c930cc 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -2,7 +2,7 @@ "invalid-data": "Datos no válidos", "not-logged-in": "No has iniciado sesión.", "account-locked": "Tu cuenta ha sido bloqueada temporalmente.", - "search-requires-login": "Searching requires an account - please login or register.", + "search-requires-login": "¡Buscar requiere estar registrado! Por favor, entra o regístrate.", "invalid-cid": "Identificador de categoría no válido", "invalid-tid": "Identificador de tema no válido", "invalid-pid": "Identificador de publicación no válido", @@ -21,11 +21,11 @@ "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.", "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": "Confirmation email already sent, please wait %1 minute(s) to send another one.", + "confirm-email-already-sent": "El email de confirmación ya ha sido enviado, por favor espera %1 minuto(s) para enviar otro.", "username-too-short": "Nombre de usuario es demasiado corto", "username-too-long": "Nombre de usuario demasiado largo", "user-banned": "Usuario baneado", - "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", + "user-too-new": "Lo sentimos, es necesario que esperes %1 segundo(s) antes poder hacer tu primera publicación", "no-category": "La categoría no existe", "no-topic": "El tema no existe", "no-post": "La publicación no existe", @@ -36,17 +36,17 @@ "no-emailers-configured": "No se ha cargado ningún plugin de email, así que no se pudo enviar el email de prueba.", "category-disabled": "Categoría deshabilitada", "topic-locked": "Tema bloqueado", - "post-edit-duration-expired": "You are only allowed to edit posts for %1 second(s) after posting", + "post-edit-duration-expired": "Sólo puedes editar mensajes durante %1 segundo(s) después de haberlo escrito", "still-uploading": "Por favor, espera a que terminen las subidas.", - "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).", - "title-too-long": "Please enter a shorter title. Titles can't be longer than %1 character(s).", - "too-many-posts": "You can only post once every %1 second(s) - please wait before posting again", - "too-many-posts-newbie": "As a new user, you can only post once every %1 second(s) until you have earned %2 reputation - please wait before posting again", - "tag-too-short": "Please enter a longer tag. Tags should contain at least %1 character(s)", - "tag-too-long": "Please enter a shorter tag. Tags can't be longer than %1 character(s)", - "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file", + "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).", + "title-too-long": "Por favor, introduce un título más corto, que no sobrepase los %1 caractere(s).", + "too-many-posts": "Solo puedes publicar una vez cada %1 segundo(s) - por favor espere antes de volver a publicar", + "too-many-posts-newbie": "Como nuevo usuario, solo puedes publicar una vez cada %1 segundo(s) hasta hayas ganado una reputación de %2 - por favor espera antes de volver a publicar", + "tag-too-short": "Por favor introduce una etiqueta más larga. Las etiquetas deben contener por lo menos %1 caractere(s)", + "tag-too-long": "Por favor introduce una etiqueta más corta. Las etiquetas no pueden exceder los %1 caractere(s)", + "file-too-big": "El tamaño de fichero máximo es de %1 kB - por favor, suba un fichero más pequeño", "cant-vote-self-post": "No puedes votar tus propios posts", "already-favourited": "Ya ha marcado esta publicación como favorita", "already-unfavourited": "Ya ha desmarcado esta publicación como favorita", @@ -63,12 +63,12 @@ "post-already-restored": "Esta publicación ya ha sido restaurada", "topic-already-deleted": "Este tema ya ha sido borrado", "topic-already-restored": "Este tema ya ha sido restaurado", - "cant-purge-main-post": "You can't purge the main post, please delete the topic instead", + "cant-purge-main-post": "No puedes purgar el mensaje principal, por favor utiliza borrar tema", "topic-thumbnails-are-disabled": "Las miniaturas de los temas están deshabilitadas.", "invalid-file": "Archivo no válido", "uploads-are-disabled": "Las subidas están deshabilitadas.", - "signature-too-long": "Sorry, your signature cannot be longer than %1 character(s).", - "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", + "signature-too-long": "Lo sentimos, pero tu firma no puede ser más larga de %1 caractere(s).", + "about-me-too-long": "Lo sentimos, pero tu descripción no puede ser más larga de %1 caractere(s).", "cant-chat-with-yourself": "¡No puedes conversar contigo mismo!", "chat-restricted": "Este usuario tiene restringidos los mensajes de chat. Los usuarios deben seguirte antes de que pueda charlar con ellos", "too-many-messages": "Has enviado demasiados mensajes, por favor espera un poco.", diff --git a/public/language/es/register.json b/public/language/es/register.json index 4ffa4d8bdd..064f05fa64 100644 --- a/public/language/es/register.json +++ b/public/language/es/register.json @@ -1,5 +1,5 @@ { - "register": "Registrase", + "register": "Registrarse", "help.email": "Por defecto, tu cuenta de correo electrónico estará oculta al publico.", "help.username_restrictions": "El nombre de usuario debe tener entre %1 y %2 carácteres. Los miembros pueden responderte escribiendo @usuario.", "help.minimum_password_length": "Tu contraseña debe tener al menos %1 carácteres.", @@ -11,7 +11,7 @@ "password_placeholder": "Introduce tu contraseña", "confirm_password": "Confirmar contraseña", "confirm_password_placeholder": "Confirmar contraseña", - "register_now_button": "Registrarme ahora", + "register_now_button": "Registrarse ahora", "alternative_registration": "Métodos de registro alternativos", "terms_of_use": "Términos y Condiciones de uso", "agree_to_terms_of_use": "Acepto los Términos y Condiciones de uso" diff --git a/public/language/es/topic.json b/public/language/es/topic.json index edf271b351..c90bccb9d0 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -5,7 +5,7 @@ "no_topics_found": "¡No se encontraron temas!", "no_posts_found": "¡No se encontraron publicaciones!", "post_is_deleted": "¡Esta publicación está eliminada!", - "topic_is_deleted": "This topic is deleted!", + "topic_is_deleted": "¡Este tema ha sido eliminado!", "profile": "Perfil", "posted_by": "Publicado por %1", "posted_by_guest": "Publicado por Invitado", diff --git a/public/language/es/user.json b/public/language/es/user.json index 44baffb856..10fef1a69b 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -21,7 +21,7 @@ "watched": "Visto", "followers": "Seguidores", "following": "Siguiendo", - "aboutme": "About me", + "aboutme": "Sobre mí", "signature": "Firma", "gravatar": "Gravatar", "birthday": "Cumpleaños", @@ -69,16 +69,16 @@ "has_no_watched_topics": "Este usuario todavía no ha visto ninguna publicación.", "email_hidden": "Correo electrónico oculto", "hidden": "oculto", - "paginate_description": "Paginate topics and posts instead of using infinite scroll", + "paginate_description": "Paginar hilos y mensajes en lugar de usar desplazamiento infinito", "topics_per_page": "Temas por página", "posts_per_page": "Post por página", - "notification_sounds": "Play a sound when you receive a notification", + "notification_sounds": "Reproducir un sonido al recibir una notificación", "browsing": "Preferencias de navegación.", - "open_links_in_new_tab": "Open outgoing links in new tab", + "open_links_in_new_tab": "Abrir los enlaces externos en una nueva pestaña", "enable_topic_searching": "Activar la búsqueda \"in-topic\"", - "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen", - "follow_topics_you_reply_to": "Follow topics that you reply to", - "follow_topics_you_create": "Follow topics you create", + "topic_search_help": "Si está activada, la búsqueda 'in-topic' sustituirá el comportamiento por defecto del navegador y le permitirá buscar en el tema al completo, en vez de hacer una búsqueda únicamente sobre el contenido mostrado en pantalla", + "follow_topics_you_reply_to": "Seguir los temas en las que respondes", + "follow_topics_you_create": "Seguir publicaciones que creas", "grouptitle": "Selecciona el título del grupo que deseas visualizar", "no-group-title": "Sin título de grupo" } \ No newline at end of file diff --git a/public/language/et/email.json b/public/language/et/email.json index 9f3a49318e..1f1c1e853a 100644 --- a/public/language/et/email.json +++ b/public/language/et/email.json @@ -1,28 +1,28 @@ { "password-reset-requested": "Parooli muutmise taotlus - %1!", - "welcome-to": "Tere tulemast %1", + "welcome-to": "Tere tulemast foorumisse %1", "greeting_no_name": "Tere", "greeting_with_name": "Tere %1", - "welcome.text1": "Thank you for registering with %1!", - "welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.", - "welcome.cta": "Click here to confirm your email address", - "reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.", - "reset.text2": "To continue with the password reset, please click on the following link:", - "reset.cta": "Click here to reset your password", - "reset.notify.subject": "Password successfully changed", - "reset.notify.text1": "We are notifying you that on %1, your password was changed successfully.", - "reset.notify.text2": "If you did not authorise this, please notify an administrator immediately.", - "digest.notifications": "You have unread notifications from %1:", - "digest.latest_topics": "Latest topics from %1", - "digest.cta": "Click here to visit %1", - "digest.unsub.info": "This digest was sent to you due to your subscription settings.", - "digest.no_topics": "There have been no active topics in the past %1", - "notif.chat.subject": "New chat message received from %1", - "notif.chat.cta": "Click here to continue the conversation", - "notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.", - "notif.post.cta": "Click here to read the full topic", - "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", - "test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.", - "unsub.cta": "Click here to alter those settings", - "closing": "Thanks!" + "welcome.text1": "Täname et oled registreerinud foorumisse %1!", + "welcome.text2": "Konto täielikuks aktiveerimiseks peame me kinnitama, et registreerimisel kasutatud e-mail kuulub teile.", + "welcome.cta": "Vajuta siia, et kinnitada oma e-maili aadress", + "reset.text1": "Meile laekus päring parooli muutmiseks. Kui päring ei ole teie poolt esitatud või te ei soovi parooli muuta, siis võite antud kirja ignoreerida.", + "reset.text2": "Selleks, et jätkata parooli muutmisega vajuta järgnevale lingile:", + "reset.cta": "Vajuta siia, et taotleda uut parooli", + "reset.notify.subject": "Parool edukalt muudetud", + "reset.notify.text1": "Teavitame sind, et sinu parool %1 foorumis on edukalt muudetud.", + "reset.notify.text2": "Kui te ei ole lubanud seda, siis teavitage koheselt administraatorit.", + "digest.notifications": "Sul on lugemata teateid %1 poolt:", + "digest.latest_topics": "Viimased teemad %1 poolt", + "digest.cta": "Vajuta siia et külastada %1", + "digest.unsub.info": "See uudiskiri on saadetud teile tellimuse seadistuse tõttu.", + "digest.no_topics": "Viimase %1 jooksul ei ole olnud ühtegi aktiivset teemat", + "notif.chat.subject": "Sulle on saabunud uus sõnum kasutajalt %1", + "notif.chat.cta": "Vajuta siia, et jätkata vestlusega", + "notif.chat.unsub.info": "See chat teavitus on saadetud teile tellimuse seadistuse tõttu.", + "notif.post.cta": "Vajuta siia, et lugeda teemat täies mahus", + "notif.post.unsub.info": "See postituse teavitus on saadetud teile tellimuse seadistuse tõttu.", + "test.text1": "See on test e-mail kinnitamaks, et emailer on korrektselt seadistatud sinu NodeBB jaoks.", + "unsub.cta": "Vajuta siia, et muuta neid seadeid", + "closing": "Aitäh!" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index dd79c11b72..dd012bc393 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -67,8 +67,8 @@ "topic-thumbnails-are-disabled": "Les miniatures de sujet sont désactivés", "invalid-file": "Fichier invalide", "uploads-are-disabled": "Les envois sont désactivés", - "signature-too-long": "Sorry, your signature cannot be longer than %1 character(s).", - "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).", + "signature-too-long": "La signature ne peut dépasser %1 caractère(s).", + "about-me-too-long": "Votre texte \"à propos de moi\" ne peut dépasser %1 caractère(s).", "cant-chat-with-yourself": "Vous ne pouvez chatter avec vous même !", "chat-restricted": "Cet utilisateur a restreint les ses messages de chat. Il doit d'abord vous suivre avant de pouvoir discuter avec lui.", "too-many-messages": "Vous avez envoyé trop de messages, veuillez patienter un instant.", diff --git a/public/language/ru/user.json b/public/language/ru/user.json index be8dea688f..755cbce9f3 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -21,7 +21,7 @@ "watched": "Просмотров", "followers": "Читателей", "following": "Читаемых", - "aboutme": "About me", + "aboutme": "Обо мне", "signature": "Подпись", "gravatar": "Gravatar", "birthday": "День рождения", diff --git a/public/language/sv/category.json b/public/language/sv/category.json index 722a8d0c45..d82abe3a24 100644 --- a/public/language/sv/category.json +++ b/public/language/sv/category.json @@ -1,6 +1,6 @@ { "new_topic_button": "Nytt ämne", - "guest-login-post": "Log in to post", + "guest-login-post": "Logga in för att posta", "no_topics": "Det finns inga ämnen i denna kategori.
Varför skapar inte du ett ämne?", "browsing": "läser", "no_replies": "Ingen har svarat", diff --git a/public/language/sv/email.json b/public/language/sv/email.json index 808c73caa8..410aea5b6a 100644 --- a/public/language/sv/email.json +++ b/public/language/sv/email.json @@ -9,9 +9,9 @@ "reset.text1": "Vi fick en förfrågan om att återställa ditt lösenord, möjligen för att du har glömt det. Om detta inte är fallet, så kan du bortse från det här epostmeddelandet. ", "reset.text2": "För att fortsätta med återställning av lösenordet så kan du klicka på följande länk:", "reset.cta": "Klicka här för att återställa ditt lösenord", - "reset.notify.subject": "Password successfully changed", - "reset.notify.text1": "We are notifying you that on %1, your password was changed successfully.", - "reset.notify.text2": "If you did not authorise this, please notify an administrator immediately.", + "reset.notify.subject": "Lösenordet ändrat", + "reset.notify.text1": "Vi vill uppmärksamma dig på att ditt lösenord ändrades den %1", + "reset.notify.text2": "Om du inte godkänt det här så vänligen kontakta en admin snarast. ", "digest.notifications": "Du har olästa notiser från %1:", "digest.latest_topics": "Senaste ämnen från %1", "digest.cta": "Klicka här för att besöka %1", @@ -20,8 +20,8 @@ "notif.chat.subject": "Nytt chatt-meddelande från %1", "notif.chat.cta": "Klicka här för att fortsätta konversationen", "notif.chat.unsub.info": "Denna chatt-notifikation skickades till dig på grund av dina inställningar för prenumerationer.", - "notif.post.cta": "Click here to read the full topic", - "notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.", + "notif.post.cta": "Klicka här för att läsa hela ämnet", + "notif.post.unsub.info": "Det här meddelandet fick du på grund av dina inställningar för prenumeration. ", "test.text1": "\nDet här är ett textmeddelande som verifierar att eposten är korrekt installerat för din NodeBB. ", "unsub.cta": "Klicka här för att ändra dom inställningarna", "closing": "Tack!" diff --git a/public/language/sv/login.json b/public/language/sv/login.json index c96dd64485..363da51e9b 100644 --- a/public/language/sv/login.json +++ b/public/language/sv/login.json @@ -1,6 +1,6 @@ { - "username-email": "Username / Email", - "username": "Username", + "username-email": "Användarnamn eller epostadress", + "username": "Användarnamn", "email": "Email", "remember_me": "Kom ihåg mig?", "forgot_password": "Glömt lösenord?", diff --git a/public/language/sv/search.json b/public/language/sv/search.json index 7822eeae96..92ef77c3d1 100644 --- a/public/language/sv/search.json +++ b/public/language/sv/search.json @@ -1,6 +1,6 @@ { "results_matching": "%1 resultat matchar \"%2\", (%3 sekunder)", - "no-matches": "No matches found", + "no-matches": "Inga träffar", "advanced-search": "Advanced Search", "in": "In", "titles": "Titles", diff --git a/public/language/sv/user.json b/public/language/sv/user.json index dd8f04d712..9131f6ff9d 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -2,8 +2,8 @@ "banned": "Bannad", "offline": "Offline", "username": "Användarnamn", - "joindate": "Join Date", - "postcount": "Post Count", + "joindate": "Gick med", + "postcount": "Antal inlägg", "email": "Epost", "confirm_email": "Bekräfta epostadress ", "delete_account": "Ta bort ämne", @@ -18,17 +18,17 @@ "profile_views": "Profil-visningar", "reputation": "Rykte", "favourites": "Favoriter", - "watched": "Watched", + "watched": "Bevakad", "followers": "Följare", "following": "Följer", - "aboutme": "About me", + "aboutme": "Om mig", "signature": "Signatur", "gravatar": "Gravatar", "birthday": "Födelsedag", "chat": "Chatta", "follow": "Följ", "unfollow": "Sluta följ", - "more": "More", + "more": "Mer", "profile_update_success": "Profilen uppdaterades.", "change_picture": "Ändra bild", "edit": "Ändra", @@ -60,25 +60,25 @@ "digest_weekly": "Veckovis", "digest_monthly": "Månadsvis", "send_chat_notifications": "Skicka ett epostmeddelande om nya chatt-meddelanden tas emot när jag inte är online.", - "send_post_notifications": "Send an email when replies are made to topics I am subscribed to", - "settings-require-reload": "Some setting changes require a reload. Click here to reload the page.", + "send_post_notifications": "Skicka ett epost när svar kommit på ämnen jag prenumererar på till", + "settings-require-reload": "Vissa inställningar som ändrades kräver att sidan laddas om. Klicka här för att ladda om sidan.", "has_no_follower": "Denna användare har inga följare :(", "follows_no_one": "Denna användare följer ingen :(", "has_no_posts": "Denna användare har inte gjort några inlägg än.", "has_no_topics": "Den här användaren har inte skrivit något inlägg ännu.", - "has_no_watched_topics": "This user didn't watch any topics yet.", + "has_no_watched_topics": "Den här användaren bevakar inga ämnen ännu.", "email_hidden": "Epost dold", "hidden": "dold", - "paginate_description": "Paginate topics and posts instead of using infinite scroll", + "paginate_description": "Gör så att ämnen och inlägg visas som sidor istället för oändlig skroll", "topics_per_page": "Ämnen per sida", "posts_per_page": "Inlägg per sida", - "notification_sounds": "Play a sound when you receive a notification", + "notification_sounds": "Spela ett ljud när du får en notis", "browsing": "Inställning för bläddring", - "open_links_in_new_tab": "Open outgoing links in new tab", + "open_links_in_new_tab": "Öppna utgående länkar på ny flik", "enable_topic_searching": "Aktivera Sökning Inom Ämne", - "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen", - "follow_topics_you_reply_to": "Follow topics that you reply to", - "follow_topics_you_create": "Follow topics you create", - "grouptitle": "Select the group title you would like to display", - "no-group-title": "No group title" + "topic_search_help": "Om aktiverat kommer sökning inom ämne överskrida webbläsarens vanliga funktionen för sökning bland sidor och tillåta dig att söka genom hela ämnet istället för det som endast visas på skärmen.", + "follow_topics_you_reply_to": "Följ ämnen som du svarat på", + "follow_topics_you_create": "Följ ämnen du skapat", + "grouptitle": "Välj tittel för gruppen så som du vill att den ska visas", + "no-group-title": "Ingen titel på gruppen" } \ No newline at end of file From 3b891ed7d87ab4659135e1de18f6d687c274c1b1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 23 May 2015 19:14:37 -0400 Subject: [PATCH 009/317] closes #3173 --- public/language/ru/reset_password.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/ru/reset_password.json b/public/language/ru/reset_password.json index ee6b29edb7..0a0c439606 100644 --- a/public/language/ru/reset_password.json +++ b/public/language/ru/reset_password.json @@ -2,7 +2,7 @@ "reset_password": "Восстановить пароль", "update_password": "Изменить пароль", "password_changed.title": "Пароль изменен", - "password_changed.message": "

Пароль успешно восстановлен, пожалуйста войдите еще раз.", + "password_changed.message": "

Пароль успешно восстановлен, пожалуйста войдите еще раз.", "wrong_reset_code.title": "Неверный код восстановления", "wrong_reset_code.message": "Неправильный код восстановления пароля. Попробуйте еще раз, или запросите новый код восстановления.", "new_password": "Новый пароль", From 4b4be3d4cced8b9fd2f92d8b6a67a97e1cef819c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 23 May 2015 19:30:31 -0400 Subject: [PATCH 010/317] fix minSchemaDate --- src/upgrade.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/upgrade.js b/src/upgrade.js index 6399a4ff32..2abdfe426c 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -17,7 +17,7 @@ var db = require('./database'), Upgrade = {}, - minSchemaDate = Date.UTC(2015, 1, 8), // This value gets updated every new MINOR version + minSchemaDate = Date.UTC(2015, 0, 30), // This value gets updated every new MINOR version schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema From 9ed88b7bb95859c898b70b4d3a6f5cfe45d61dea Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sat, 23 May 2015 22:08:17 -0400 Subject: [PATCH 011/317] updated readme a bit --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d8b5435dbc..f54187be2f 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,12 @@ Additional functionality is enabled through the use of third-party plugins. * [Get NodeBB](http://www.nodebb.org/ "NodeBB") * [Demo & Meta Discussion](http://community.nodebb.org) -* [NodeBB Blog](http://blog.nodebb.org) * [Documentation & Installation Instructions](http://docs.nodebb.org) +* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/) +* [NodeBB Blog](http://blog.nodebb.org) * [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode * [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") * [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") -* [Get Plugins](http://community.nodebb.org/category/7/nodebb-plugins "NodeBB Plugins") -* [Get Themes](http://community.nodebb.org/category/10/nodebb-themes "NodeBB Themes") -* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/) ## Screenshots @@ -62,4 +60,6 @@ Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.node ## License -NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html) +NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html). + +Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org. \ No newline at end of file From 714c7356f98481ea64b4cfa5c35c483640c14365 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 23 May 2015 22:11:20 -0400 Subject: [PATCH 012/317] closes #3176 --- install/data/defaults.json | 134 ++++++++----------------------------- src/install.js | 39 +++++++---- 2 files changed, 55 insertions(+), 118 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index 9a0d6eb1ec..4e5ebc035e 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -1,106 +1,28 @@ -[ - { - "field": "title", - "value": "NodeBB" - }, - { - "field": "showSiteTitle", - "value": "1" - }, - { - "field": "postDelay", - "value": 10 - }, - { - "field": "initialPostDelay", - "value": 10 - }, - { - "field": "newbiePostDelay", - "value": 120 - }, - { - "field": "newbiePostDelayThreshold", - "value": 3 - }, - { - "field": "minimumPostLength", - "value": 8 - }, - { - "field": "maximumPostLength", - "value": 32767 - }, - { - "field": "allowGuestSearching", - "value": 0 - }, - { - "field": "allowTopicsThumbnail", - "value": 0 - }, - { - "field": "allowRegistration", - "value": 1 - }, - { - "field": "allowLocalLogin", - "value": 1 - }, - { - "field": "allowAccountDelete", - "value": 1 - }, - { - "field": "allowFileUploads", - "value": 0 - }, - { - "field": "maximumFileSize", - "value": 2048 - }, - { - "field": "minimumTitleLength", - "value": 3 - }, - { - "field": "maximumTitleLength", - "value": 255 - }, - { - "field": "minimumUsernameLength", - "value": 2 - }, - { - "field": "maximumUsernameLength", - "value": 16 - }, - { - "field": "minimumPasswordLength", - "value": 6 - }, - { - "field": "maximumSignatureLength", - "value": 255 - }, - { - "field": "maximumAboutMeLength", - "value": 1000 - }, - { - "field": "maximumProfileImageSize", - "value": 256 - }, - { - "field": "profileImageDimension", - "value": 128 - }, - { - "field": "requireEmailConfirmation", - "value": 0 - }, - { - "field": "profile:allowProfileImageUploads", - "value": 1 - } -] +{ + "title": "NodeBB", + "showSiteTitle": 1, + "postDelay": 10, + "initialPostDelay": 10, + "newbiePostDelay": 120, + "newbiePostDelayThreshold": 3, + "minimumPostLength": 8, + "maximumPostLength": 32767, + "allowGuestSearching": 0, + "allowTopicsThumbnail": 0, + "allowRegistration": 1, + "allowLocalLogin": 1, + "allowAccountDelete": 1, + "allowFileUploads": 0, + "maximumFileSize": 2048, + "minimumTitleLength": 3, + "maximumTitleLength": 255, + "minimumUsernameLength": 2, + "maximumUsernameLength": 16, + "minimumPasswordLength": 6, + "maximumSignatureLength": 255, + "maximumAboutMeLength": 1000, + "maximumProfileImageSize": 256, + "profileImageDimension": 128, + "requireEmailConfirmation": 0, + "profile:allowProfileImageUploads": 1 +} diff --git a/src/install.js b/src/install.js index 8b7c97ffc5..93597029f6 100644 --- a/src/install.js +++ b/src/install.js @@ -239,24 +239,39 @@ function setupDefaultConfigs(next) { var meta = require('./meta'), defaults = require(path.join(__dirname, '../', 'install/data/defaults.json')); - async.each(defaults, function (configObj, next) { - meta.configs.setOnEmpty(configObj.field, configObj.value, next); + async.each(Object.keys(defaults), function (key, next) { + meta.configs.setOnEmpty(key, defaults[key], next); }, function (err) { - meta.configs.init(next); - }); + if (err) { + return next(err); + } - if (install.values) { - setIfPaired('social:twitter:key', 'social:twitter:secret'); - setIfPaired('social:google:id', 'social:google:secret'); - setIfPaired('social:facebook:app_id', 'social:facebook:secret'); - } + if (install.values) { + async.parallel([ + async.apply(setIfPaired, 'social:twitter:key', 'social:twitter:secret'), + async.apply(setIfPaired, 'social:google:id', 'social:google:secret'), + async.apply(setIfPaired, 'social:facebook:app_id', 'social:facebook:secret') + ], function(err) { + if (err) { + return next(err); + } + meta.configs.init(next); + }); + } else { + meta.configs.init(next); + } + }); } -function setIfPaired(key1, key2) { +function setIfPaired(key1, key2, callback) { var meta = require('./meta'); if (install.values[key1] && install.values[key2]) { - meta.configs.setOnEmpty(key1, install.values[key1]); - meta.configs.setOnEmpty(key2, install.values[key2]); + async.parallel([ + async.apply(meta.configs.setOnEmpty, key1, install.values[key1]), + async.apply(meta.configs.setOnEmpty, key2, install.values[key2]) + ], callback); + } else { + callback(); } } From 349de1694d4759154983a18a3ccfa96380b9c3d1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 24 May 2015 10:15:16 -0400 Subject: [PATCH 013/317] fixed log line in web installer --- install/web.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/install/web.js b/install/web.js index 0587238244..9d53349724 100644 --- a/install/web.js +++ b/install/web.js @@ -41,8 +41,7 @@ web.install = function(port) { function launchExpress(port) { server = app.listen(port, function() { - var host = server.address().address; - winston.info('Web installer listening on http://%s:%s', host, port); + winston.info('Web installer listening on http://%s:%s', '0.0.0.0', port); }); } From b2f2561e745b044117c9b2a8d74bbb8df0f26f24 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 24 May 2015 10:39:52 -0400 Subject: [PATCH 014/317] adding a bit of logging when launching NodeBB from the web installer --- install/web.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/install/web.js b/install/web.js index 9d53349724..673e9c22fd 100644 --- a/install/web.js +++ b/install/web.js @@ -103,6 +103,9 @@ function launch(req, res) { stdio: ['ignore', 'ignore', 'ignore'] }); + process.stdout.write('\nStarting NodeBB\n'); + process.stdout.write(' "./nodebb stop" to stop the NodeBB server\n'); + process.stdout.write(' "./nodebb log" to view server output\n'); child.unref(); process.exit(0); From d8e52d7ebf6b40aabc04f45f8f6bf0cd4daa67b5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 25 May 2015 13:27:59 -0400 Subject: [PATCH 015/317] closes https://github.com/NodeBB/nodebb-theme-persona/issues/87 --- src/socket.io/categories.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 8ed8881f68..dd2e130010 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -78,6 +78,11 @@ SocketCategories.loadMore = function(socket, data, callback) { } data.privileges = results.privileges; + data.template = { + category: true, + name: 'category' + }; + callback(null, data); }); }); From a0a8d328d0b05421fd70295d0ab9388ad2687c45 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 25 May 2015 14:05:52 -0400 Subject: [PATCH 016/317] closes #3182 --- src/install.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/install.js b/src/install.js index 93597029f6..ed9616ffc3 100644 --- a/src/install.js +++ b/src/install.js @@ -422,10 +422,17 @@ function createCategories(next) { } function createMenuItems(next) { - var navigation = require('./navigation/admin'), - data = require('../install/data/navigation.json'); + var db = require('./database'); - navigation.save(data, next); + db.exists('navigation:enabled', function(err, exists) { + if (err || exists) { + return next(err); + } + var navigation = require('./navigation/admin'), + data = require('../install/data/navigation.json'); + + navigation.save(data, next); + }); } function createWelcomePost(next) { From fc2efb0c83ad46aad5ee6b9047aee440a772915e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 May 2015 14:47:54 -0400 Subject: [PATCH 017/317] added one more line to stdout when nodebb status is invoked, or nodebb web installer finishes --- install/web.js | 1 + nodebb | 1 + 2 files changed, 2 insertions(+) diff --git a/install/web.js b/install/web.js index 673e9c22fd..0c7fb2521f 100644 --- a/install/web.js +++ b/install/web.js @@ -106,6 +106,7 @@ function launch(req, res) { process.stdout.write('\nStarting NodeBB\n'); process.stdout.write(' "./nodebb stop" to stop the NodeBB server\n'); process.stdout.write(' "./nodebb log" to view server output\n'); + process.stdout.write(' "./nodebb restart" to restart NodeBB\n'); child.unref(); process.exit(0); diff --git a/nodebb b/nodebb index 6e0e4f679e..d45163ee32 100755 --- a/nodebb +++ b/nodebb @@ -28,6 +28,7 @@ case "$1" in echo "Starting NodeBB"; echo " \"./nodebb stop\" to stop the NodeBB server"; echo " \"./nodebb log\" to view server output"; + echo " \"./nodebb restart\" to restart NodeBB"; # Start the loader daemon "$node" loader "$@" From e6061810f938c70a654a71e108b0ef4fee38d213 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 May 2015 16:06:49 -0400 Subject: [PATCH 018/317] updating nodebb executable so that it is a node script instead of bash script --- nodebb | 244 +++++++++++++++++++++++++-------------------------- package.json | 10 ++- 2 files changed, 124 insertions(+), 130 deletions(-) diff --git a/nodebb b/nodebb index d45163ee32..dd05fbf841 100755 --- a/nodebb +++ b/nodebb @@ -1,138 +1,130 @@ -#!/bin/bash +#!/usr/bin/env node -# $0 script path -# $1 action -# $2 subaction +var colors = require('colors'), + cproc = require('child_process'), + argv = require('minimist')(process.argv.slice(2)), + fs = require('fs'); -node="$(which nodejs 2>/dev/null)"; -if [ $? -gt 0 ]; - then node="$(which node)"; -fi +var getRunningPid = function(callback) { + fs.readFile(__dirname + '/pidfile', { + encoding: 'utf-8' + }, function(err, pid) { + if (err) { + return callback(err); + } -function pidExists() { - if [ -e "pidfile" ]; - then - if ps -p $(cat pidfile) > /dev/null - then return 1; - else - rm ./pidfile; - return 0; - fi - else - return 0; - fi + try { + process.kill(parseInt(pid, 10), 0); + callback(null, parseInt(pid, 10)); + } catch(e) { + callback(e); + } + }); + }; + +switch(process.argv[2]) { + case 'status': + getRunningPid(function(err, pid) { + if (!err) { + process.stdout.write('\nNodeBB Running '.bold + '(pid '.cyan + pid.toString().cyan + ')\n'.cyan); + process.stdout.write('\t"' + './nodebb stop'.yellow + '" to stop the NodeBB server\n'); + process.stdout.write('\t"' + './nodebb log'.yellow + '" to view server output\n'); + process.stdout.write('\t"' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'); + } else { + process.stdout.write('\nNodeBB is not running\n'.bold); + process.stdout.write('\t"' + './nodebb start'.yellow + '" to launch the NodeBB server\n\n'); + } + }) + break; + + case 'start': + process.stdout.write('\nStarting NodeBB\n'.bold); + process.stdout.write(' "' + './nodebb stop'.yellow + '" to stop the NodeBB server\n'); + process.stdout.write(' "' + './nodebb log'.yellow + '" to view server output\n'); + process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'); + + // Spawn a new NodeBB process + cproc.fork(__dirname + '/loader.js', { + env: process.env, + detatched: true + }); + break; + + case 'stop': + getRunningPid(function(err, pid) { + if (!err) { + process.kill(pid, 'SIGTERM'); + process.stdout.write('Stopping NodeBB. Goodbye!\n') + } else { + process.stdout.write('NodeBB is already stopped.\n'); + } + }); + break; + + case 'restart': + getRunningPid(function(err, pid) { + if (!err) { + process.kill(pid, 'SIGHUP'); + } else { + process.stdout.write('NodeBB could not be restarted, as a running instance could not be found.'); + } + }); + break; + + case 'reload': + getRunningPid(function(err, pid) { + if (!err) { + process.kill(pid, 'SIGUSR2'); + } else { + process.stdout.write('NodeBB could not be reloaded, as a running instance could not be found.'); + } + }); + break; + + case 'dev': + process.env.NODE_ENV = 'development'; + cproc.fork(__dirname + '/loader.js', ['--no-daemon', '--no-silent'], { + env: process.env + }); + break; + + default: + process.stdout.write('\nWelcome to NodeBB\n\n'.bold); + process.stdout.write('Usage: ./nodebb {start|stop|reload|restart|log|setup|reset|upgrade|dev}\n\n'); + process.stdout.write('\t' + 'start'.yellow + '\tStart the NodeBB server\n'); + process.stdout.write('\t' + 'stop'.yellow + '\tStops the NodeBB server\n'); + process.stdout.write('\t' + 'reload'.yellow + '\tRestarts NodeBB\n'); + process.stdout.write('\t' + 'restart'.yellow + '\tRestarts NodeBB\n'); + process.stdout.write('\t' + 'log'.yellow + '\tOpens the logging interface (useful for debugging)\n'); + process.stdout.write('\t' + 'setup'.yellow + '\tRuns the NodeBB setup script\n'); + process.stdout.write('\t' + 'reset'.yellow + '\tDisables all plugins, restores the default theme.\n'); + process.stdout.write('\t' + 'upgrade'.yellow + '\tRun NodeBB upgrade scripts, ensure packages are up-to-date\n'); + process.stdout.write('\t' + 'dev'.yellow + '\tStart NodeBB in interactive development mode\n'); + process.stdout.write('\t' + 'watch'.yellow + '\tStart NodeBB in development mode and watch for changes\n'); + process.stdout.write('\n'); + break; } -case "$1" in - start) - echo "Starting NodeBB"; - echo " \"./nodebb stop\" to stop the NodeBB server"; - echo " \"./nodebb log\" to view server output"; - echo " \"./nodebb restart\" to restart NodeBB"; - - # Start the loader daemon - "$node" loader "$@" - ;; - - stop) - pidExists; - if [ 0 -eq $? ]; - then - echo "NodeBB is already stopped."; - else - echo "Stopping NodeBB. Goodbye!"; - kill $(cat pidfile); - fi - ;; - - restart) - pidExists; - if [ 0 -eq $? ]; - then - echo "NodeBB could not be restarted, as a running instance could not be found."; - else - echo "Restarting NodeBB."; - kill -1 $(cat pidfile); - fi - ;; - - reload) - pidExists; - if [ 0 -eq $? ]; - then - echo "NodeBB could not be reloaded, as a running instance could not be found."; - else - echo "Reloading NodeBB."; - kill -12 $(cat pidfile); - fi - ;; - - status) - pidExists; - if [ 0 -eq $? ]; - then - echo "NodeBB is not running"; - echo " \"./nodebb start\" to launch the NodeBB server"; - else - echo "NodeBB Running (pid $(cat pidfile))"; - echo " \"./nodebb stop\" to stop the NodeBB server"; - echo " \"./nodebb log\" to view server output"; - echo " \"./nodebb restart\" to restart NodeBB"; - fi - ;; - +/* log) - clear; - tail -F ./logs/output.log; - ;; + clear; + tail -F ./logs/output.log; + ;; upgrade) - npm install - # ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm install - # ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm update - npm i nodebb-theme-vanilla nodebb-theme-lavender nodebb-widget-essentials - "$node" app --upgrade - touch package.json - ;; + npm install + # ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm install + # ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm update + npm i nodebb-theme-vanilla nodebb-theme-lavender nodebb-widget-essentials + "$node" app --upgrade + touch package.json + ;; setup) - "$node" app --setup "$@" - ;; + "$node" app --setup "$@" + ;; reset) - "$node" app --reset --$2 - ;; - - dev) - echo "Launching NodeBB in \"development\" mode." - echo "To run the production build of NodeBB, please use \"forever\"." - echo "More Information: https://docs.nodebb.org/en/latest/running/index.html" - NODE_ENV=development "$node" loader --no-daemon --no-silent "$@" - ;; - - watch) - echo "***************************************************************************" - echo "WARNING: ./nodebb watch will be deprecated soon. Please use grunt: " - echo "https://docs.nodebb.org/en/latest/running/index.html#grunt-development" - echo "***************************************************************************" - NODE_ENV=development supervisor -q --ignore public/templates,public/nodebb.min.js,public/nodebb.min.js.map --extensions 'node|js|tpl|less' -- app "$@" - ;; - - *) - echo "Welcome to NodeBB" - echo $"Usage: $0 {start|stop|reload|restart|log|setup|reset|upgrade|dev|watch}" - echo '' - column -s ' ' -t <<< ' - start Start the NodeBB server - stop Stops the NodeBB server - reload Restarts NodeBB - restart Restarts NodeBB - log Opens the logging interface (useful for debugging) - setup Runs the NodeBB setup script - reset Disables all plugins, restores the default theme. - upgrade Run NodeBB upgrade scripts, ensure packages are up-to-date - dev Start NodeBB in interactive development mode - watch Start NodeBB in development mode and watch for changes - ' - exit 1 -esac + "$node" app --reset --$2 + ;; +*/ \ No newline at end of file diff --git a/package.json b/package.json index 27d4933f94..6fd4275862 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "async": "~0.9.0", "bcryptjs": "~2.1.0", "body-parser": "^1.9.0", + "colors": "^1.1.0", "compression": "^1.1.0", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", @@ -34,6 +35,7 @@ "logrotate-stream": "^0.2.3", "lru-cache": "^2.6.1", "mime": "^1.3.4", + "minimist": "^1.1.1", "mkdirp": "~0.5.0", "mmmagic": "^0.3.13", "morgan": "^1.3.2", @@ -44,11 +46,11 @@ "nodebb-plugin-mentions": "^0.11.2", "nodebb-plugin-soundpack-default": "^0.1.1", "nodebb-plugin-spam-be-gone": "^0.4.0", - "nodebb-theme-lavender": "^1.0.42", - "nodebb-theme-vanilla": "^1.0.130", - "nodebb-theme-persona": "^0.1.55", - "nodebb-widget-essentials": "^1.0.2", "nodebb-rewards-essentials": "^0.0.1", + "nodebb-theme-lavender": "^1.0.42", + "nodebb-theme-persona": "^0.1.55", + "nodebb-theme-vanilla": "^1.0.130", + "nodebb-widget-essentials": "^1.0.2", "npm": "^2.1.4", "passport": "^0.2.1", "passport-local": "1.0.0", From 4a0bc1fb030e50f6b6e05254b1bb865e8856d18c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 25 May 2015 21:08:05 -0400 Subject: [PATCH 019/317] finished up executable rewrite, and prettified reset script output --- app.js | 34 +++++++++++++------- nodebb | 88 +++++++++++++++++++++++++++++++++++++--------------- package.json | 1 + 3 files changed, 86 insertions(+), 37 deletions(-) diff --git a/app.js b/app.js index 8ff5647023..4e677df7c1 100644 --- a/app.js +++ b/app.js @@ -29,6 +29,7 @@ var fs = require('fs'), async = require('async'), semver = require('semver'), winston = require('winston'), + colors = require('colors'), path = require('path'), pkg = require('./package.json'), utils = require('./public/src/utils.js'); @@ -274,17 +275,19 @@ function reset() { process.exit(); } - if (nconf.get('theme')) { + if (nconf.get('t')) { resetThemes(); - } else if (nconf.get('plugin')) { - resetPlugin(nconf.get('plugin')); - } else if (nconf.get('plugins')) { - resetPlugins(); - } else if (nconf.get('widgets')) { + } else if (nconf.get('p')) { + if (nconf.get('p') === true) { + resetPlugins(); + } else { + resetPlugin(nconf.get('p')); + } + } else if (nconf.get('w')) { resetWidgets(); - } else if (nconf.get('settings')) { + } else if (nconf.get('s')) { resetSettings(); - } else if (nconf.get('all')) { + } else if (nconf.get('a')) { require('async').series([resetWidgets, resetThemes, resetPlugins, resetSettings], function(err) { if (!err) { winston.info('[reset] Reset complete.'); @@ -294,10 +297,17 @@ function reset() { process.exit(); }); } else { - winston.warn('[reset] Nothing reset.'); - winston.info('Use ./nodebb reset {theme|plugins|widgets|settings|all}'); - winston.info(' or'); - winston.info('Use ./nodebb reset plugin="nodebb-plugin-pluginName"'); + process.stdout.write('\nNodeBB Reset\n'.bold); + process.stdout.write('No arguments passed in, so nothing was reset.\n\n'.yellow); + process.stdout.write('Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}\n'.red); + process.stdout.write(' -t\tthemes\n'); + process.stdout.write(' -p\tplugins\n'); + process.stdout.write(' -w\twidgets\n'); + process.stdout.write(' -s\tsettings\n'); + process.stdout.write(' -a\tall of the above\n'); + + process.stdout.write('\nPlugin reset flag (-p) can take a single argument\n'); + process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions\n'); process.exit(); } }); diff --git a/nodebb b/nodebb index dd05fbf841..37a783ec79 100755 --- a/nodebb +++ b/nodebb @@ -3,7 +3,10 @@ var colors = require('colors'), cproc = require('child_process'), argv = require('minimist')(process.argv.slice(2)), - fs = require('fs'); + fs = require('fs'), + async = require('async'), + touch = require('touch'), + npm = require('npm'); var getRunningPid = function(callback) { fs.readFile(__dirname + '/pidfile', { @@ -88,6 +91,65 @@ switch(process.argv[2]) { }); break; + case 'log': + process.stdout.write('\nType '.red + 'Ctrl-C '.bold + 'to exit\n\n'.red); + cproc.spawn('tail', ['-F', './logs/output.log'], { + cwd: __dirname, + stdio: 'inherit' + }); + break; + + case 'setup': + cproc.fork('app.js', ['--setup'], { + cwd: __dirname, + silent: false + }); + break; + + case 'reset': + var args = process.argv.slice(0); + args.unshift('--reset'); + + cproc.fork('app.js', args, { + cwd: __dirname, + silent: false + }); + break; + + case 'upgrade': + async.series([ + function(next) { + process.stdout.write('1. '.bold + 'Bringing base dependencies up to date\n'.yellow); + npm.load({ + loglevel: 'silent' + }, function() { + npm.commands.install(next); + }); + }, + function(next) { + process.stdout.write('2. '.bold + 'Updating NodeBB data store schema\n'.yellow); + var upgradeProc = cproc.fork('app.js', ['--upgrade'], { + cwd: __dirname, + silent: false + }); + + upgradeProc.on('close', next) + }, + function(next) { + process.stdout.write('3. '.bold + 'Storing upgrade date in "package.json"\n'.yellow); + touch(__dirname + '/package.json', {}, next); + } + ], function(err) { + if (err) { + process.stdout.write('\nError'.red + ': ' + err.message + '\n'); + } else { + var message = 'NodeBB Upgrade Complete!', + spaces = new Array(Math.floor(process.stdout.columns / 2) - (message.length / 2) + 1).join(' '); + process.stdout.write('\n' + spaces + message.green.bold + '\n\n'); + } + }); + break; + default: process.stdout.write('\nWelcome to NodeBB\n\n'.bold); process.stdout.write('Usage: ./nodebb {start|stop|reload|restart|log|setup|reset|upgrade|dev}\n\n'); @@ -104,27 +166,3 @@ switch(process.argv[2]) { process.stdout.write('\n'); break; } - -/* - log) - clear; - tail -F ./logs/output.log; - ;; - - upgrade) - npm install - # ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm install - # ls -d node_modules/nodebb* | xargs -n1 basename | xargs npm update - npm i nodebb-theme-vanilla nodebb-theme-lavender nodebb-widget-essentials - "$node" app --upgrade - touch package.json - ;; - - setup) - "$node" app --setup "$@" - ;; - - reset) - "$node" app --reset --$2 - ;; -*/ \ No newline at end of file diff --git a/package.json b/package.json index 6fd4275862..908cda8d9c 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "socketio-wildcard": "~0.1.1", "string": "^3.0.0", "templates.js": "^0.2.3", + "touch": "0.0.3", "uglify-js": "git+https://github.com/julianlam/UglifyJS2.git", "underscore": "~1.8.3", "validator": "^3.30.0", From cbb05429847ae882c71a5a4f0324b62f9454f002 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 May 2015 10:51:23 -0400 Subject: [PATCH 020/317] changed behaviour of privilege table so that groups without explicit privileges are not shown in the privilege table --- src/privileges/categories.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/privileges/categories.js b/src/privileges/categories.js index 59b2f79a1d..4572de2e92 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -101,19 +101,25 @@ module.exports = function(privileges) { groupNames.splice(0, 0, groupNames.splice(groupNames.indexOf('registered-users'), 1)[0]); groupNames.splice(groupNames.indexOf('administrators'), 1); - var memberData = groupNames.filter(function(member) { + var memberPrivs, boolSet, + memberData = groupNames.filter(function(member) { return member.indexOf(':privileges:') === -1; }).map(function(member) { - var memberPrivs = {}; + memberPrivs = {}; + boolSet = []; // Here, the boolSet is used as a quick way to determine whether a given group's privilege set is empty or not (see below) for(var x=0,numPrivs=privileges.length;x Date: Tue, 26 May 2015 11:52:34 -0400 Subject: [PATCH 021/317] allowing array of privileges to be passed into setPrivilege in category admin socket listener --- src/socket.io/admin/categories.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index 4db4445d4d..848d175a09 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -59,7 +59,13 @@ Categories.setPrivilege = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + data.privilege, data.member, callback); + if (Array.isArray(data.privilege)) { + async.each(data.privilege, function(privilege, next) { + groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + privilege, data.member, next); + }, callback); + } else { + groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + data.privilege, data.member, callback); + } }; Categories.getPrivilegeSettings = function(socket, cid, callback) { From 8f7416d1cb074655c9ebcb848468ff687e69eed4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 May 2015 12:35:27 -0400 Subject: [PATCH 022/317] updated acp category privilege settings so that not all groups are shown in privilege table, closes #3036 --- public/less/admin/admin.less | 5 + public/src/admin/manage/category.js | 83 +++++++++--- public/src/modules/autocomplete.js | 31 +++++ src/views/admin/manage/category.tpl | 6 +- .../admin/partials/categories/privileges.tpl | 123 +++++++++++------- 5 files changed, 175 insertions(+), 73 deletions(-) diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index 40ddcf08e3..8ab5c633e9 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -283,6 +283,11 @@ #taskbar { display: none; /* not sure why I have to do this, but it only seems to show up on prod */ } + + /* Allows the autocomplete dropbox to appear on top of a modal's backdrop */ + .ui-autocomplete { + z-index: @zindex-popover; + } } // Allowing text to the right of an image-type brand diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index 238317144c..6524ed8b37 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -158,26 +158,6 @@ define('admin/manage/category', [ }; Category.setupPrivilegeTable = function() { - var searchEl = $('.privilege-search'), - searchObj = autocomplete.user(searchEl); - - // User search + addition to table - searchObj.on('autocompleteselect', function(ev, ui) { - socket.emit('admin.categories.setPrivilege', { - cid: ajaxify.variables.get('cid'), - privilege: 'read', - set: true, - member: ui.item.user.uid - }, function(err) { - if (err) { - return app.alertError(err.message); - } - - Category.refreshPrivilegeTable(); - searchEl.val(''); - }); - }); - // Checkbox event capture $('.privilege-table-container').on('change', 'input[type="checkbox"]', function() { var checkboxEl = $(this), @@ -205,6 +185,9 @@ define('admin/manage/category', [ } }); + $('.privilege-table-container').on('click', '[data-action="search.user"]', Category.addUserToPrivilegeTable); + $('.privilege-table-container').on('click', '[data-action="search.group"]', Category.addGroupToPrivilegeTable); + Category.exposeAssumedPrivileges(); }; @@ -292,5 +275,65 @@ define('admin/manage/category', [ }); }; + Category.addUserToPrivilegeTable = function() { + var modal = bootbox.dialog({ + title: 'Find a User', + message: '', + show: true + }); + + modal.on('shown.bs.modal', function() { + var inputEl = modal.find('input'), + searchObj = autocomplete.user(inputEl); + + searchObj.on('autocompleteselect', function(ev, ui) { + socket.emit('admin.categories.setPrivilege', { + cid: ajaxify.variables.get('cid'), + privilege: ['find', 'read'], + set: true, + member: ui.item.user.uid + }, function(err) { + if (err) { + return app.alertError(err.message); + } + + Category.refreshPrivilegeTable(); + modal.modal('hide'); + }); + }); + }); + }; + + Category.addGroupToPrivilegeTable = function() { + var modal = bootbox.dialog({ + title: 'Find a Group', + message: '', + show: true + }); + + modal.on('shown.bs.modal', function() { + var inputEl = modal.find('input'), + searchObj = autocomplete.group(inputEl); + + searchObj.on('autocompleteselect', function(ev, ui) { + console.log(ui); + socket.emit('admin.categories.setPrivilege', { + cid: ajaxify.variables.get('cid'), + privilege: ['groups:find', 'groups:read'], + set: true, + member: ui.item.group.name + }, function(err) { + console.log(arguments); + if (err) { + return app.alertError(err.message); + } + + Category.refreshPrivilegeTable(); + modal.modal('hide'); + }); + }); + }); + }; + return Category; }); \ No newline at end of file diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js index 55c15b8686..8a6967a308 100644 --- a/public/src/modules/autocomplete.js +++ b/public/src/modules/autocomplete.js @@ -35,5 +35,36 @@ define('autocomplete', function() { }); }; + module.group = function(input) { + return input.autocomplete({ + delay: 100, + source: function(request, response) { + socket.emit('groups.search', { + query: request.term, + options: {} + }, function(err, results) { + if (err) { + return app.alertError(err.message); + } + + if (results && results.length) { + var names = results.map(function(group) { + return group && { + label: group.name, + value: group.name, + group: { + name: group.name, + slug: group.slug + } + }; + }); + response(names); + } + $('.ui-autocomplete a').attr('data-ajaxify', 'false'); + }); + } + }); + }; + return module; }); diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl index 80b5dbe694..48d07b0e6c 100644 --- a/src/views/admin/manage/category.tpl +++ b/src/views/admin/manage/category.tpl @@ -86,9 +86,9 @@ these settings.


- -
- +
+ +
diff --git a/src/views/admin/partials/categories/privileges.tpl b/src/views/admin/partials/categories/privileges.tpl index 7e1f97b580..eb97e6bcac 100644 --- a/src/views/admin/partials/categories/privileges.tpl +++ b/src/views/admin/partials/categories/privileges.tpl @@ -1,51 +1,74 @@ -
- - - - - - - - - - - - - {function.spawnPrivilegeStates, privileges.users.username, privileges} - - - - - - - -
User{privileges.labels.users.name}
{privileges.users.username}
-
No user-specific privileges in this category.
-
+ + + + + + + + + + + + + {function.spawnPrivilegeStates, privileges.users.username, privileges} + + + + + + + + + + +
User{privileges.labels.users.name}
{privileges.users.username}
+ +
+
+ + No user-specific privileges in this category. +
+
- - - - - - - - - - - {function.spawnPrivilegeStates, name, privileges} - - -
Group{privileges.labels.groups.name}
- - - - {privileges.groups.name} -
-
- If the registered-users group is granted a specific privilege, all other groups receive an - implicit privilege, even if they are not explicitly defined/checked. This implicit - privilege is shown to you because all users are part of the registered-users user group, - and so, privileges for additional groups need not be explicitly granted. -
-
\ No newline at end of file + + + + + + + + + + + + + {function.spawnPrivilegeStates, name, privileges} + + + + + + + + + + +
Group{privileges.labels.groups.name}
+ + + + {privileges.groups.name} +
+ +
+
+ + No group-specific privileges in this category. +
+
+
+ If the registered-users group is granted a specific privilege, all other groups receive an + implicit privilege, even if they are not explicitly defined/checked. This implicit + privilege is shown to you because all users are part of the registered-users user group, + and so, privileges for additional groups need not be explicitly granted. +
From 8a225ad719fef6cda4ef1bcbf0dfdaaf309fa00d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 May 2015 12:37:31 -0400 Subject: [PATCH 023/317] starting groups organization --- src/groups/create.js | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/groups/create.js diff --git a/src/groups/create.js b/src/groups/create.js new file mode 100644 index 0000000000..efabac3fc5 --- /dev/null +++ b/src/groups/create.js @@ -0,0 +1,68 @@ +'use strict'; + +var async = require('async'), + meta = require('../meta'), + plugins = require('../plugins'), + utils = require('../../public/src/utils'), + db = require('./../database'); + +module.exports = function(Groups) { + Groups.create = function(data, callback) { + if (data.name.length === 0) { + return callback(new Error('[[error:group-name-too-short]]')); + } + + if (data.name === 'administrators' || data.name === 'registered-users' || Groups.internals.isPrivilegeGroup.test(data.name)) { + var system = true; + } + + meta.userOrGroupExists(data.name, function (err, exists) { + if (err) { + return callback(err); + } + + if (exists) { + return callback(new Error('[[error:group-already-exists]]')); + } + var timestamp = data.timestamp || Date.now(); + + var slug = utils.slugify(data.name), + groupData = { + name: data.name, + slug: slug, + createtime: timestamp, + userTitle: data.name, + description: data.description || '', + memberCount: 0, + deleted: '0', + hidden: data.hidden || '0', + system: system ? '1' : '0', + private: data.private || '1' + }, + tasks = [ + async.apply(db.sortedSetAdd, 'groups:createtime', timestamp, data.name), + async.apply(db.setObject, 'group:' + data.name, groupData) + ]; + + if (data.hasOwnProperty('ownerUid')) { + tasks.push(async.apply(db.setAdd, 'group:' + data.name + ':owners', data.ownerUid)); + tasks.push(async.apply(db.sortedSetAdd, 'group:' + data.name + ':members', timestamp, data.ownerUid)); + tasks.push(async.apply(db.setObjectField, 'group:' + data.name, 'memberCount', 1)); + + groupData.ownerUid = data.ownerUid; + } + + if (!data.hidden) { + tasks.push(async.apply(db.setObjectField, 'groupslug:groupname', slug, data.name)); + } + + async.series(tasks, function(err) { + if (!err) { + plugins.fireHook('action:group.create', groupData); + } + + callback(err, groupData); + }); + }); + }; +}; From 3f1726636fdbe40107c99715fc1c76c106bba924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 May 2015 13:17:49 -0400 Subject: [PATCH 024/317] groups create/delete/update --- src/groups.js | 261 ++----------------------------------------- src/groups/create.js | 2 +- src/groups/delete.js | 42 +++++++ src/groups/update.js | 165 +++++++++++++++++++++++++++ 4 files changed, 220 insertions(+), 250 deletions(-) create mode 100644 src/groups/delete.js create mode 100644 src/groups/update.js diff --git a/src/groups.js b/src/groups.js index 802f4346e2..7763353764 100644 --- a/src/groups.js +++ b/src/groups.js @@ -22,7 +22,11 @@ var async = require('async'), (function(Groups) { - var ephemeralGroups = ['guests'], + require('./groups/create')(Groups); + require('./groups/delete')(Groups); + require('./groups/update')(Groups); + + var ephemeralGroups = ['guests'], internals = { filterGroups: function(groups, options) { @@ -63,10 +67,14 @@ var async = require('async'), } return groups; - }, - isPrivilegeGroup: /^cid:\d+:privileges:[\w:]+$/ + } }; + var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/; + Groups.isPrivilegeGroup = function(groupName) { + return isPrivilegeGroupRegex.test(groupName); + }; + Groups.getEphemeralGroups = function() { return ephemeralGroups; }; @@ -497,256 +505,11 @@ var async = require('async'), } }; - Groups.create = function(data, callback) { - if (data.name.length === 0) { - return callback(new Error('[[error:group-name-too-short]]')); - } - - if (data.name === 'administrators' || data.name === 'registered-users' || internals.isPrivilegeGroup.test(data.name)) { - var system = true; - } - - meta.userOrGroupExists(data.name, function (err, exists) { - if (err) { - return callback(err); - } - - if (exists) { - return callback(new Error('[[error:group-already-exists]]')); - } - var timestamp = data.timestamp || Date.now(); - - var slug = utils.slugify(data.name), - groupData = { - name: data.name, - slug: slug, - createtime: timestamp, - userTitle: data.name, - description: data.description || '', - memberCount: 0, - deleted: '0', - hidden: data.hidden || '0', - system: system ? '1' : '0', - private: data.private || '1' - }, - tasks = [ - async.apply(db.sortedSetAdd, 'groups:createtime', timestamp, data.name), - async.apply(db.setObject, 'group:' + data.name, groupData) - ]; - - if (data.hasOwnProperty('ownerUid')) { - tasks.push(async.apply(db.setAdd, 'group:' + data.name + ':owners', data.ownerUid)); - tasks.push(async.apply(db.sortedSetAdd, 'group:' + data.name + ':members', timestamp, data.ownerUid)); - tasks.push(async.apply(db.setObjectField, 'group:' + data.name, 'memberCount', 1)); - - groupData.ownerUid = data.ownerUid; - } - - if (!data.hidden) { - tasks.push(async.apply(db.setObjectField, 'groupslug:groupname', slug, data.name)); - } - - async.series(tasks, function(err) { - if (!err) { - plugins.fireHook('action:group.create', groupData); - } - - callback(err, groupData); - }); - }); - }; - Groups.hide = function(groupName, callback) { callback = callback || function() {}; db.setObjectField('group:' + groupName, 'hidden', 1, callback); }; - Groups.update = function(groupName, values, callback) { - callback = callback || function() {}; - db.exists('group:' + groupName, function (err, exists) { - if (err || !exists) { - return callback(err || new Error('[[error:no-group]]')); - } - - var payload = { - description: values.description || '', - icon: values.icon || '', - labelColor: values.labelColor || '#000000' - }; - - if (values.hasOwnProperty('userTitle')) { - payload.userTitle = values.userTitle || ''; - } - - if (values.hasOwnProperty('userTitleEnabled')) { - payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0'; - } - - if (values.hasOwnProperty('hidden')) { - payload.hidden = values.hidden ? '1' : '0'; - } - - if (values.hasOwnProperty('private')) { - payload.private = values.private ? '1' : '0'; - } - - async.series([ - async.apply(updatePrivacy, groupName, values.private), - async.apply(db.setObject, 'group:' + groupName, payload), - async.apply(renameGroup, groupName, values.name) - ], function(err) { - if (err) { - return callback(err); - } - - plugins.fireHook('action:group.update', { - name: groupName, - values: values - }); - callback(); - }); - }); - }; - - function updatePrivacy(groupName, newValue, callback) { - if (!newValue) { - return callback(); - } - - Groups.getGroupFields(groupName, ['private'], function(err, currentValue) { - if (err) { - return callback(err); - } - currentValue = currentValue.private === '1'; - - if (currentValue !== newValue && currentValue === true) { - // Group is now public, so all pending users are automatically considered members - db.getSetMembers('group:' + groupName + ':pending', function(err, uids) { - if (err) { return callback(err); } - else if (!uids) { return callback(); } // No pending users, we're good to go - - var now = Date.now(), - scores = uids.map(function() { return now; }); // There's probably a better way to initialise an Array of size x with the same value... - - winston.verbose('[groups.update] Group is now public, automatically adding ' + uids.length + ' new members, who were pending prior.'); - async.series([ - async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', scores, uids), - async.apply(db.delete, 'group:' + groupName + ':pending') - ], callback); - }); - } else { - callback(); - } - }); - } - - function renameGroup(oldName, newName, callback) { - if (oldName === newName || !newName || newName.length === 0) { - return callback(); - } - - db.getObject('group:' + oldName, function(err, group) { - if (err || !group) { - return callback(err); - } - - if (parseInt(group.system, 10) === 1 || parseInt(group.hidden, 10) === 1) { - return callback(); - } - - Groups.exists(newName, function(err, exists) { - if (err || exists) { - return callback(err || new Error('[[error:group-already-exists]]')); - } - - async.series([ - async.apply(db.setObjectField, 'group:' + oldName, 'name', newName), - async.apply(db.setObjectField, 'group:' + oldName, 'slug', utils.slugify(newName)), - async.apply(db.deleteObjectField, 'groupslug:groupname', group.slug), - async.apply(db.setObjectField, 'groupslug:groupname', utils.slugify(newName), newName), - function(next) { - db.getSortedSetRange('groups:createtime', 0, -1, function(err, groups) { - if (err) { - return next(err); - } - async.each(groups, function(group, next) { - renameGroupMember('group:' + group + ':members', oldName, newName, next); - }, next); - }); - }, - async.apply(db.rename, 'group:' + oldName, 'group:' + newName), - async.apply(db.rename, 'group:' + oldName + ':members', 'group:' + newName + ':members'), - async.apply(db.rename, 'group:' + oldName + ':owners', 'group:' + newName + ':owners'), - async.apply(db.rename, 'group:' + oldName + ':pending', 'group:' + newName + ':pending'), - async.apply(db.rename, 'group:' + oldName + ':invited', 'group:' + newName + ':invited'), - async.apply(renameGroupMember, 'groups:createtime', oldName, newName), - function(next) { - plugins.fireHook('action:group.rename', { - old: oldName, - new: newName - }); - - next(); - } - ], callback); - }); - }); - } - - function renameGroupMember(group, oldName, newName, callback) { - db.isSortedSetMember(group, oldName, function(err, isMember) { - if (err || !isMember) { - return callback(err); - } - var score; - async.waterfall([ - function (next) { - db.sortedSetScore(group, oldName, next); - }, - function (_score, next) { - score = _score; - db.sortedSetRemove(group, oldName, next); - }, - function (next) { - db.sortedSetAdd(group, score, newName, next); - } - ], callback); - }); - } - - Groups.destroy = function(groupName, callback) { - Groups.getGroupsData([groupName], function(err, groupsData) { - if (err) { - return callback(err); - } - if (!Array.isArray(groupsData) || !groupsData[0]) { - return callback(); - } - var groupObj = groupsData[0]; - plugins.fireHook('action:group.destroy', groupObj); - - async.parallel([ - async.apply(db.delete, 'group:' + groupName), - async.apply(db.sortedSetRemove, 'groups:createtime', groupName), - async.apply(db.delete, 'group:' + groupName + ':members'), - async.apply(db.delete, 'group:' + groupName + ':pending'), - async.apply(db.delete, 'group:' + groupName + ':invited'), - async.apply(db.delete, 'group:' + groupName + ':owners'), - async.apply(db.deleteObjectField, 'groupslug:groupname', utils.slugify(groupName)), - function(next) { - db.getSortedSetRange('groups:createtime', 0, -1, function(err, groups) { - if (err) { - return next(err); - } - async.each(groups, function(group, next) { - db.sortedSetRemove('group:' + group + ':members', groupName, next); - }, next); - }); - } - ], callback); - }); - }; - Groups.join = function(groupName, uid, callback) { function join() { var tasks = [ @@ -1159,7 +922,7 @@ var async = require('async'), function(users, next) { var uids = []; for(var i=0; i Date: Tue, 26 May 2015 13:44:52 -0400 Subject: [PATCH 025/317] dont call groups.get just to read 2 values --- src/groups.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/groups.js b/src/groups.js index 7763353764..e6acafcd80 100644 --- a/src/groups.js +++ b/src/groups.js @@ -647,13 +647,12 @@ var async = require('async'), uid: uid }); - // If this is a hidden group, and it is now empty, delete it - Groups.get(groupName, {}, function(err, group) { - if (err || !group) { + Groups.getGroupFields(groupName, ['hidden', 'memberCount'], function(err, groupData) { + if (err || !groupData) { return callback(err); } - if (group.hidden && group.memberCount === 0) { + if (parseInt(groupData.hidden, 10) === 1 && parseInt(groupData.memberCount, 10) === 0) { Groups.destroy(groupName, callback); } else { callback(); From 8300aeec3529fbf45a9b37649393309970ab052a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 26 May 2015 13:55:40 -0400 Subject: [PATCH 026/317] parseInt member count, use getMemberCount in install js --- src/groups.js | 7 ++++++- src/install.js | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/groups.js b/src/groups.js index e6acafcd80..0c667c9871 100644 --- a/src/groups.js +++ b/src/groups.js @@ -363,7 +363,12 @@ var async = require('async'), }; Groups.getMemberCount = function(groupName, callback) { - db.getObjectField('group:' + groupName, 'memberCount', callback); + db.getObjectField('group:' + groupName, 'memberCount', function(err, count) { + if (err) { + return callback(err); + } + callback(null, parseInt(count, 10)); + }); }; Groups.isMemberOfGroupList = function(uid, groupListKey, callback) { diff --git a/src/install.js b/src/install.js index ed9616ffc3..9aafa5d598 100644 --- a/src/install.js +++ b/src/install.js @@ -294,8 +294,11 @@ function enableDefaultTheme(next) { function createAdministrator(next) { var Groups = require('./groups'); - Groups.get('administrators', {}, function (err, groupObj) { - if (!err && groupObj && groupObj.memberCount > 0) { + Groups.getMemberCount('administrators', function (err, memberCount) { + if (err) { + return next(err); + } + if (memberCount > 0) { process.stdout.write('Administrator found, skipping Admin setup\n'); next(); } else { From aca5d24a7d83916fcf4ebf22560a7e05c34a30c2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 26 May 2015 14:45:17 -0400 Subject: [PATCH 027/317] split groups.js into more subsidiary files --- src/groups.js | 482 +-------------------------------------- src/groups/membership.js | 317 +++++++++++++++++++++++++ src/groups/ownership.js | 31 +++ src/groups/search.js | 86 +++++++ src/groups/update.js | 73 +++++- 5 files changed, 510 insertions(+), 479 deletions(-) create mode 100644 src/groups/membership.js create mode 100644 src/groups/ownership.js create mode 100644 src/groups/search.js diff --git a/src/groups.js b/src/groups.js index 0c667c9871..17d39c413b 100644 --- a/src/groups.js +++ b/src/groups.js @@ -2,8 +2,6 @@ var async = require('async'), winston = require('winston'), - _ = require('underscore'), - crypto = require('crypto'), path = require('path'), nconf = require('nconf'), fs = require('fs'), @@ -16,15 +14,16 @@ var async = require('async'), posts = require('./posts'), privileges = require('./privileges'), utils = require('../public/src/utils'), - util = require('util'), - - uploadsController = require('./controllers/uploads'); + util = require('util'); (function(Groups) { require('./groups/create')(Groups); require('./groups/delete')(Groups); require('./groups/update')(Groups); + require('./groups/membership')(Groups); + require('./groups/ownership')(Groups); + require('./groups/search')(Groups); var ephemeralGroups = ['guests'], @@ -70,6 +69,8 @@ var async = require('async'), } }; + Groups.internals = internals; + var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/; Groups.isPrivilegeGroup = function(groupName) { return isPrivilegeGroupRegex.test(groupName); @@ -330,144 +331,6 @@ var async = require('async'), }); }; - Groups.getMembers = function(groupName, start, stop, callback) { - db.getSortedSetRevRange('group:' + groupName + ':members', start, stop, callback); - }; - - Groups.getMembersOfGroups = function(groupNames, callback) { - db.getSortedSetsMembers(groupNames.map(function(name) { - return 'group:' + name + ':members'; - }), callback); - }; - - Groups.isMember = function(uid, groupName, callback) { - if (!uid || parseInt(uid, 10) <= 0) { - return callback(null, false); - } - db.isSortedSetMember('group:' + groupName + ':members', uid, callback); - }; - - Groups.isMembers = function(uids, groupName, callback) { - db.isSortedSetMembers('group:' + groupName + ':members', uids, callback); - }; - - Groups.isMemberOfGroups = function(uid, groups, callback) { - if (!uid || parseInt(uid, 10) <= 0) { - return callback(null, groups.map(function() {return false;})); - } - groups = groups.map(function(groupName) { - return 'group:' + groupName + ':members'; - }); - - db.isMemberOfSortedSets(groups, uid, callback); - }; - - Groups.getMemberCount = function(groupName, callback) { - db.getObjectField('group:' + groupName, 'memberCount', function(err, count) { - if (err) { - return callback(err); - } - callback(null, parseInt(count, 10)); - }); - }; - - Groups.isMemberOfGroupList = function(uid, groupListKey, callback) { - db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function(err, groupNames) { - if (err) { - return callback(err); - } - groupNames = internals.removeEphemeralGroups(groupNames); - if (groupNames.length === 0) { - return callback(null, false); - } - - Groups.isMemberOfGroups(uid, groupNames, function(err, isMembers) { - if (err) { - return callback(err); - } - - callback(null, isMembers.indexOf(true) !== -1); - }); - }); - }; - - Groups.isMemberOfGroupsList = function(uid, groupListKeys, callback) { - var sets = groupListKeys.map(function(groupName) { - return 'group:' + groupName + ':members'; - }); - - db.getSortedSetsMembers(sets, function(err, members) { - if (err) { - return callback(err); - } - - var uniqueGroups = _.unique(_.flatten(members)); - uniqueGroups = internals.removeEphemeralGroups(uniqueGroups); - - Groups.isMemberOfGroups(uid, uniqueGroups, function(err, isMembers) { - if (err) { - return callback(err); - } - - var map = {}; - - uniqueGroups.forEach(function(groupName, index) { - map[groupName] = isMembers[index]; - }); - - var result = members.map(function(groupNames) { - for (var i=0; i