diff --git a/app.js b/app.js index 6fd215ad61..2158d61282 100644 --- a/app.js +++ b/app.js @@ -285,6 +285,11 @@ function upgrade() { function activate() { require('./src/database').init(function(err) { + if (err) { + winston.error(err.stack); + process.exit(1); + } + var plugin = nconf.get('_')[1] ? nconf.get('_')[1] : nconf.get('activate'), db = require('./src/database'); @@ -296,9 +301,19 @@ function activate() { function listPlugins() { require('./src/database').init(function(err) { + if (err) { + winston.error(err.stack); + process.exit(1); + } + var db = require('./src/database'); db.getSortedSetRange('plugins:active', 0, -1, function(err, plugins) { + if (err) { + winston.error(err.stack); + process.exit(1); + } + winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - ')); process.exit(); }); diff --git a/install/web.js b/install/web.js index 0f4e645e8a..b60614de07 100644 --- a/install/web.js +++ b/install/web.js @@ -130,6 +130,10 @@ function compileLess(callback) { } fs.readFile(path.join(__dirname, '../public/less/install.less'), function(err, style) { + if (err) { + return winston.error('Unable to read LESS install file: ', err); + } + less.render(style.toString(), function(err, css) { if(err) { return winston.error('Unable to compile LESS: ', err); diff --git a/loader.js b/loader.js index 56e4864289..78e3f34a7f 100644 --- a/loader.js +++ b/loader.js @@ -207,7 +207,8 @@ function getPorts() { Loader.restart = function() { killWorkers(); - + nconf.remove('file'); + nconf.use('file', { file: path.join(__dirname, '/config.json') }); Loader.start(); }; diff --git a/package.json b/package.json index 0e412b0336..9bbb2f50b9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "cron": "^1.0.5", "csurf": "^1.6.1", "daemon": "~1.1.0", - "express": "^4.9.5", + "express": "^4.14.0", "express-session": "^1.8.2", "express-useragent": "0.2.4", "html-to-text": "2.0.0", @@ -57,8 +57,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.20", - "nodebb-theme-vanilla": "5.1.9", + "nodebb-theme-persona": "4.1.23", + "nodebb-theme-vanilla": "5.1.11", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", @@ -74,7 +74,7 @@ "semver": "^5.1.0", "serve-favicon": "^2.1.5", "sitemap": "^1.4.0", - "socket.io": "^1.4.0", + "socket.io": "^1.4.8", "socket.io-client": "^1.4.0", "socket.io-redis": "^1.0.0", "socketio-wildcard": "~0.3.0", @@ -117,4 +117,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 4717871d44..f1a81bf0c3 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/ar/global.json b/public/language/ar/global.json index 75516c6d09..96356a3bce 100644 --- a/public/language/ar/global.json +++ b/public/language/ar/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 6a3ef24696..48d57a8dee 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Можете да изтривате публикациите си до %1 час(а) и %2 минута/и, след като ги пуснете", "post-delete-duration-expired-days": "Можете да изтривате публикациите си до %1 ден(а), след като ги пуснете", "post-delete-duration-expired-days-hours": "Можете да изтривате публикациите си до %1 ден(а) и %2 час(а), след като ги пуснете", + "cant-delete-topic-has-reply": "Не можете да изтриете темата си, след като в нея вече има един отговор", + "cant-delete-topic-has-replies": "Не можете да изтриете темата си, след като в нея вече има %1 отговора", "content-too-short": "Моля, въведете по-дълъг текст на публикацията. Публикациите трябва да съдържат поне %1 символ(а).", "content-too-long": "Моля, въведете по-кратък текст на публикацията. Публикациите трябва да съдържат не повече от %1 символ(а).", "title-too-short": "Моля, въведете по-дълго заглавие. Заглавията трябва да съдържат поне %1 символ(а).", diff --git a/public/language/bg/global.json b/public/language/bg/global.json index 8993ea3df3..189c8d3990 100644 --- a/public/language/bg/global.json +++ b/public/language/bg/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Въведете номер на страница", "upload_file": "Качване на файл", "upload": "Качване", - "allowed-file-types": "Разрешените файлови типове са: %1" + "allowed-file-types": "Разрешените файлови типове са: %1", + "unsaved-changes": "Имате незапазени промени. Наистина ли искате да напуснете тази страница?" } \ No newline at end of file diff --git a/public/language/bn/error.json b/public/language/bn/error.json index 6a07599afa..66e8758fcb 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/bn/global.json b/public/language/bn/global.json index abc0e8cda4..bfde1d9003 100644 --- a/public/language/bn/global.json +++ b/public/language/bn/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 7162bf5ebd..1c27c9e522 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/cs/global.json b/public/language/cs/global.json index 83860b52cc..d78d3797f4 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Zadejte číslo stránky", "upload_file": "Nahrár soubor", "upload": "Nahrát", - "allowed-file-types": "Povolené typy souborů jsou %1" + "allowed-file-types": "Povolené typy souborů jsou %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/da/error.json b/public/language/da/error.json index 78e2b469c3..9be724b215 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Venligst indtast et længere indlæg. Indlægget skal mindst indeholde %1 karakter(er).", "content-too-long": "Venligt indtast et kortere indlæg. Indlæg kan ikke være længere end %1 karakter(er).", "title-too-short": "Venligst indtast en længere titel. Titlen skal mindst indeholde %1 karakter(er).", diff --git a/public/language/da/global.json b/public/language/da/global.json index b918c4d51c..2eda261502 100644 --- a/public/language/da/global.json +++ b/public/language/da/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Indsæt sideantal", "upload_file": "Upload fil", "upload": "Upload", - "allowed-file-types": "Tilladte filtyper er %1" + "allowed-file-types": "Tilladte filtyper er %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/de/error.json b/public/language/de/error.json index 544aead1c3..d10bf13c3e 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Bitte schreibe einen längeren Beitrag. Beiträge sollten mindestens %1 Zeichen enthalten.", "content-too-long": "Bitte schreibe einen kürzeren Beitrag. Beiträge können nicht länger als %1 Zeichen sein.", "title-too-short": "Bitte gebe einen längeren Titel ein. Ein Titel muss mindestens %1 Zeichen enthalten.", diff --git a/public/language/de/global.json b/public/language/de/global.json index cddeca336f..496c212943 100644 --- a/public/language/de/global.json +++ b/public/language/de/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Seitennummer eingeben", "upload_file": "Datei hochladen", "upload": "Hochladen", - "allowed-file-types": "Erlaubte Dateitypen sind %1" + "allowed-file-types": "Erlaubte Dateitypen sind %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/el/error.json b/public/language/el/error.json index b7f9c621ed..32c609f78b 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/el/global.json b/public/language/el/global.json index e8b8255a1a..32eacb6466 100644 --- a/public/language/el/global.json +++ b/public/language/el/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 6a614b6de4..2ec9878ee8 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/en@pirate/global.json b/public/language/en@pirate/global.json index 1b78cd651b..6c86c89999 100644 --- a/public/language/en@pirate/global.json +++ b/public/language/en@pirate/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 319f5005b6..b6ddd1a2bb 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -153,5 +153,8 @@ "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", "no-users-selected": "No user(s) selected", - "invalid-home-page-route": "Invalid home page route" + "invalid-home-page-route": "Invalid home page route", + + "invalid-session": "Session Mismatch", + "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." } diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index 6641c28261..b8cadf0bc6 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -7,8 +7,10 @@ "403.login": "Perhaps you should try logging in?", "404.title": "Not Found", "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.", - "500.title": "Internal error.", + "500.title": "Internal Error.", "500.message": "Oops! Looks like something went wrong!", + "400.title": "Bad Request.", + "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.", "register": "Register", "login": "Login", diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index 6a614b6de4..2ec9878ee8 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/en_US/global.json b/public/language/en_US/global.json index 802a81d4bb..e7f511d90a 100644 --- a/public/language/en_US/global.json +++ b/public/language/en_US/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/es/email.json b/public/language/es/email.json index ddc0a47556..6bb5dd06f8 100644 --- a/public/language/es/email.json +++ b/public/language/es/email.json @@ -24,7 +24,7 @@ "digest.day": "día", "digest.week": "semana", "digest.month": "mes", - "digest.subject": "Resumen de 1%", + "digest.subject": "Resumen de %1", "notif.chat.subject": "Nuevo mensaje de chat recibido de %1", "notif.chat.cta": "Haz click aquí para continuar la conversación", "notif.chat.unsub.info": "Esta notificación de chat se te envió debido a tus ajustes de suscripción.", diff --git a/public/language/es/error.json b/public/language/es/error.json index 0df59adbad..f49449598c 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "No puedes borrar mensajes hasta pasado %1 hora(s) y %2 minuto(s) después de haberlo escrito", "post-delete-duration-expired-days": "No puedes borrar mensajes hasta pasado %1 día(s) después de haberlo escrito", "post-delete-duration-expired-days-hours": "No puedes borrar mensajes hasta pasado %1 día(s) y %2 hora(s) después de haberlo escrito", + "cant-delete-topic-has-reply": "No puedes borrar tu tema después de que tenga respuestas", + "cant-delete-topic-has-replies": "No puedes borrar tu tema despues de que tenga ℅1 respuestas", "content-too-short": "Por favor introduzca una publicación más larga. Las publicaciones deben contener al menos %1 caractere(s).", "content-too-long": "Por favor introduzca un mensaje más corto. Los mensajes no pueden exceder los %1 caractere(s).", "title-too-short": "Por favor introduzca un título más largo. Los títulos deben contener al menos %1 caractere(s).", diff --git a/public/language/es/global.json b/public/language/es/global.json index 79fd9c1a88..3cf4afbbae 100644 --- a/public/language/es/global.json +++ b/public/language/es/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Escribe el número de página", "upload_file": "Subir archivo", "upload": "Subir", - "allowed-file-types": "Los tipos de archivos permitidos son: %1" + "allowed-file-types": "Los tipos de archivos permitidos son: %1", + "unsaved-changes": "Tienes cambios sin guardar. Seguro que quieres salir?" } \ No newline at end of file diff --git a/public/language/es/modules.json b/public/language/es/modules.json index d47569a921..b2ce72e4ae 100644 --- a/public/language/es/modules.json +++ b/public/language/es/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Foto", "composer.upload-picture": "Subir foto", "composer.upload-file": "Subir archivo", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Modo Zen", "bootbox.ok": "OK", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/et/error.json b/public/language/et/error.json index 48024ec16a..3aed5d7967 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Palun tehke pikem postitus. Postituse pikkus peab olema vähemalt %1 tähemärk(i).", "content-too-long": "Palun tehke lühem postitus. Postituse pikkus peab olema vähem kui %1 tähemärk(i).", "title-too-short": "Palun sisesta pikem pealkiri. Pealkirjad ei saa olla lühemad kui %1 tähemärk(i).", diff --git a/public/language/et/global.json b/public/language/et/global.json index 0735277f4d..3add6a941c 100644 --- a/public/language/et/global.json +++ b/public/language/et/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Sisesta lehekülje number", "upload_file": "Lae fail üles", "upload": "Lae üles", - "allowed-file-types": "Lubatud faili formaadid on %1" + "allowed-file-types": "Lubatud faili formaadid on %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 965f4a00c2..6063b7cfd7 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "شما تنها می توانید %1 ساعت(ها) %2 دقیقه(ها) پس از فرستادن پست آن‌ را پاک کنید", "post-delete-duration-expired-days": "شما تنها می توانید %1 روز(ها) پس از فرستادن پست آن‌ را پاک کنید", "post-delete-duration-expired-days-hours": "شما تنها می توانید %1 روز(ها) %2 ساعت(ها) پس از فرستادن پست آن‌ را پاک کنید", + "cant-delete-topic-has-reply": "اگر کسی به موضوع شما پاسخ داده باشد، نمیتوانید آنرا حذف نمائید", + "cant-delete-topic-has-replies": "اگر 1% به موضوع جواب داده شده باشد ، نمیتوانید آنرا حذف نمائید", "content-too-short": "خواهشمندیم پست بلندتری بنویسید. پست‌ها دست‌کم باید %1 کاراکتر داشته باشند.", "content-too-long": "لطفا طول مطلب را کوتاه تر کنید. طول پست نمیتواند بیشتر از %1 کاراکتر باشد.", "title-too-short": "لطفا یک عنوان بلندتر وارد کنید. عنوان باید حداقل %1 کاراکتر داشته باشد.", diff --git a/public/language/fa_IR/global.json b/public/language/fa_IR/global.json index 8888c6bc17..ce48647e40 100644 --- a/public/language/fa_IR/global.json +++ b/public/language/fa_IR/global.json @@ -92,5 +92,6 @@ "enter_page_number": "شماره صفحه را وارد کنید", "upload_file": "بارگذاری فایل", "upload": "بارگذاری", - "allowed-file-types": "فایل قابل قبول اینها هستند %1" + "allowed-file-types": "فایل قابل قبول اینها هستند %1", + "unsaved-changes": "تغییرات شما ذخیره نشده. شما مطمئن هستید که میخواهید از اینجا دور شوید؟" } \ No newline at end of file diff --git a/public/language/fi/error.json b/public/language/fi/error.json index 8862df7356..536a3854a8 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Ole hyvä ja syötä pidempi viesti. Sen pitäisi sisältää ainakin %1 merkki(ä).", "content-too-long": "Ole hyvä ja syötä lyhyempi viesti. Sen voi sisältää vain %1 merkki(ä).", "title-too-short": "Ole hyä ja syötä pidempi otsikko. Sen pitäisi sisältää anakin %1 merkki(ä).", diff --git a/public/language/fi/global.json b/public/language/fi/global.json index 1a9176b1be..8488b07bfe 100644 --- a/public/language/fi/global.json +++ b/public/language/fi/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 0a494e47ab..f240a77009 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Vous ne pouvez supprimer un message que pendant %1 heure(s) et %2 minute(s) après l'avoir posté.", "post-delete-duration-expired-days": "Vous ne pouvez supprimer un message que pendant %1 jour(s) après l'avoir posté.", "post-delete-duration-expired-days-hours": "Vous ne pouvez supprimer un message que pendant %1 jour(s) et %2 heure(s) après l'avoir posté.", + "cant-delete-topic-has-reply": "Vous ne pouvez pas supprimer votre sujet s'il a au moins une réponse.", + "cant-delete-topic-has-replies": "Vous ne pouvez pas supprimer votre sujet s'il a au moins %1 réponses.", "content-too-short": "Veuillez entrer un message plus long. %1 caractère(s) minimum.", "content-too-long": "Veuillez poster un message plus cours. Les messages ne peuvent être plus long que %1 caractère(s).", "title-too-short": "Veuillez entrer un titre plus long. %1 caractère(s) minimum.", diff --git a/public/language/fr/global.json b/public/language/fr/global.json index 36209bd2fb..10b70a2148 100644 --- a/public/language/fr/global.json +++ b/public/language/fr/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Entrer un numéro de page", "upload_file": "Envoyer un fichier", "upload": "Envoyer", - "allowed-file-types": "Les types de fichiers autorisés sont : %1" + "allowed-file-types": "Les types de fichiers autorisés sont : %1", + "unsaved-changes": "Vous avez des modifications non sauvegardées. Êtes-vous sûr de vouloir naviguer tout de même ?" } \ No newline at end of file diff --git a/public/language/gl/category.json b/public/language/gl/category.json index 1ff9bc2daf..eb12b9660e 100644 --- a/public/language/gl/category.json +++ b/public/language/gl/category.json @@ -14,7 +14,7 @@ "ignoring": "Ignorando", "watching.description": "Amosa-los temas en \"non lidos\"", "ignoring.description": "Non amosa-los temas en \"non lidos\"", - "watch.message": "Agora vixías as novidades desta categoría", + "watch.message": "Agora Sigues as novidades desta categoría", "ignore.message": "Agora ignoras as novidades nesta categoría", "watched-categories": "Categorías vixiadas" } \ No newline at end of file diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 76bdf60486..d7cf7fa7da 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -20,7 +20,7 @@ "email-taken": "Correo en uso", "email-not-confirmed": "O teu correo aínda non está confirmado, por favor pica aquí para confirmalo.", "email-not-confirmed-chat": "Non podes charlar ata que confirmes o teu correo, por favor pica aquí para confirmalo.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "O teu correo electrónico está sen confirmar. Por favor, busca o correo de confirmación na túa bandexa de entrada.", "no-email-to-confirm": "Este foro require confirmación de correo, por favor pica aquí para introducir un correo.", "email-confirm-failed": "Non podemos confirmar o teu correo, por favor téntao de novo máis tarde.", "confirm-email-already-sent": "O correo de confirmación foi enviado, agarda %1 minute(s) para enviar outro.", @@ -48,13 +48,15 @@ "post-edit-duration-expired-hours-minutes": "Só podes editar as publicacións %1 hora(s) %2 segundo(s) despois de envialas. ", "post-edit-duration-expired-days": "Só podes editar as publicacións %1 día(s) despois de envialas. ", "post-edit-duration-expired-days-hours": "Só podes editar as publicacións %1 día(s) %2 hora(s) despois de envialas. ", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "post-delete-duration-expired": "Só podes borrar mensaxes %1 segundo(s) despois de escribilos.", + "post-delete-duration-expired-minutes": "Só podes borrar mensaxes %1 minuto(s) despois de escribilos.", + "post-delete-duration-expired-minutes-seconds": "Só podes borrar mensaxes %1 minuto(s) e 2% segundo(s) despois de escribilos.", + "post-delete-duration-expired-hours": "Só podes borrar mensaxes %1 hora(s) despois de escribilos.", + "post-delete-duration-expired-hours-minutes": "Só podes borrar mensaxes %1 hora(s) e %2 minuto(s) despois de escribilos.", + "post-delete-duration-expired-days": "Só podes borrar mensaxes %1 día(s) despois de escribilos.", + "post-delete-duration-expired-days-hours": "Só podes borrar mensaxes %1 día(s) e %2 hora(s) despois de escribilos.", + "cant-delete-topic-has-reply": "Non podes borrar o teu tema cando xa ten respostas", + "cant-delete-topic-has-replies": "Non podes borrar o teu tema cando xa ten %1 respostas", "content-too-short": "Por favor, introduce unha publicación máis longa. Debe conter %1 carácter(es) como mínimo.", "content-too-long": "Por favor, introduce unha publicación máis curta. As publicacións non poden conter máis de %1 carácter(es).", "title-too-short": "Por favor, introduce un título máis longo. Os títulos deben conter %1 carácter(es) como mínimo.", @@ -72,12 +74,12 @@ "already-unfavourited": "Xa desgardaras esta publicación.", "cant-ban-other-admins": "Non podes botar outros administradores!", "cant-remove-last-admin": "Eres o único administrador. Engade outros administradores antes de quitarte a ti mesmo como administrador.", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "Retirar privilexios de administrador desta conta antes de intentar borrala", "invalid-image-type": "Tipo de imaxe inválida. Tipos admitidos: %1", "invalid-image-extension": "Extensión de imaxe inválida", "invalid-file-type": "Tipo de arquivo inválido. Tipos admitidos: %1", "group-name-too-short": "Nome de grupo moi curto", - "group-name-too-long": "Group name too long", + "group-name-too-long": "Nome de grupo demasiado longo", "group-already-exists": "O grupo xa existe", "group-name-change-not-allowed": "Cambio de nome do grupo non permitido", "group-already-member": "Xa eres parte deste grupo", @@ -116,10 +118,10 @@ "wrong-login-type-email": "Por favor, emprega o teu correo para contectarte", "wrong-login-type-username": "Por favor, usa o teu nome de usuario para conectarte", "invite-maximum-met": "Convidaches á cantidade máxima de persoas (%1 de %2).", - "no-session-found": "Non se atopa ningún inicio de sesión!", - "not-in-room": "O usuario non se encontra nesta sala", + "no-session-found": "Non se atopou ningún inicio de sesión!", + "not-in-room": "O usuario non se atopa nesta sala", "no-users-in-room": "Non hai usuarios nesta sala", "cant-kick-self": "Non te podes expulsar a ti mesmo do grupo", - "no-users-selected": "No user(s) selected", - "invalid-home-page-route": "Invalid home page route" + "no-users-selected": "Ningún usuario seleccionado", + "invalid-home-page-route": "Ruta de páxina de inicio inválida" } \ No newline at end of file diff --git a/public/language/gl/global.json b/public/language/gl/global.json index 29b4471221..8d1301e078 100644 --- a/public/language/gl/global.json +++ b/public/language/gl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Escribe o número da páxina", "upload_file": "Subir arquivo ", "upload": "Subir", - "allowed-file-types": "Os tipos de arquivos permitidos son: %1" + "allowed-file-types": "Os tipos de arquivos permitidos son: %1", + "unsaved-changes": "Non gardaches tódolos cambios. Queres continuar e saír da páxina?" } \ No newline at end of file diff --git a/public/language/gl/login.json b/public/language/gl/login.json index 12530c7699..8b885cbb78 100644 --- a/public/language/gl/login.json +++ b/public/language/gl/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Erro ao iniciar sesión", "login_successful": "Sesión iniciada con éxito!", "dont_have_account": "Aínda non tes conta?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Debido a inactividade fuches desconectado do Panel de Control de Administradores" } \ No newline at end of file diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json index 9bebb20db3..3cff4867cd 100644 --- a/public/language/gl/modules.json +++ b/public/language/gl/modules.json @@ -37,7 +37,7 @@ "composer.formatting.picture": "Foto", "composer.upload-picture": "Subir foto", "composer.upload-file": "Subir arquivo", - "composer.zen_mode": "Zen Mode", + "composer.zen_mode": "Modo Zen", "bootbox.ok": "De acordo", "bootbox.cancel": "Cancelar", "bootbox.confirm": "Confirmar", diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json index ab5fe853c3..3097bfec5e 100644 --- a/public/language/gl/topic.json +++ b/public/language/gl/topic.json @@ -26,14 +26,14 @@ "tools": "Ferramentas", "flag": "Reportar", "locked": "Pechado", - "pinned": "Pinned", - "moved": "Moved", + "pinned": "Fixo", + "moved": "Movido", "bookmark_instructions": "Pica aquí para volver á última mensaxe lida neste tema ", "flag_title": "Reportar esta mensaxe", "flag_success": "Esta mensaxe foi reportada para moderación.", "deleted_message": "Este tema foi borrado. Só os usuarios con privilexios administrativos poden velo.", "following_topic.message": "Agora recibirás notificacións cando alguén publique neste tema.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Poderás ver este tema na lista de No Lidos, pero non recibirás notificacións cando alguén escriba nel.", "ignoring_topic.message": "Xa non verás este fío na lista de fíos non lidos. Serás notificado cando sexas mencionado ou a túa publicación sexa votada.", "login_to_subscribe": "Por favor, identifícate para subscribirte a este tema.", "markAsUnreadForAll.success": "Publicación marcada como non lida para todos.", @@ -42,10 +42,10 @@ "watch": "Vixiar", "unwatch": "Deixar de vixiar", "watch.title": "Serás notificado canto haxa novas respostas neste tema", - "unwatch.title": "Deixar de vixiar este tema", + "unwatch.title": "Deixar de seguir este tema", "share_this_post": "Compartir esta publicación", - "watching": "Vendo", - "not-watching": "Non Vendo", + "watching": "Seguindo", + "not-watching": "Non seguindo", "ignoring": "Ignorar", "watching.description": "Notificádeme das novas repostas.
Amosa-lo fío nos non lidos.", "not-watching.description": "Non me notifiquedes as novas respostas.
Amosa-lo fío en non lidos se a categoría non está ignorada.", @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Este tema será movido á categoría", "fork_topic_instruction": "Fai clic nas publicacións que queiras dividir", "fork_no_pids": "Non seleccionaches ninguna publicación!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 mensaxe(s) seleccionada(s)", "fork_success": "Creouse un novo tema a partir do orixinal! Fai clic aquí para ir ó novo tema.", "delete_posts_instruction": "Fai clic nas mensaxes que queres eliminar/limpar", "composer.title_placeholder": "Introduce o título do teu tema", diff --git a/public/language/he/error.json b/public/language/he/error.json index 7b00a4a288..52ee6b7549 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "אנא הכנס פוסט ארוך יותר. פוסטים חייבים להכיל לפחות %1 תווים.", "content-too-long": "אנא הכנס פוסט קצר יותר. פוסטים חייבים להיות קצרים יותר מ-%1 תווים.", "title-too-short": "אנא הכנס כותרת ארוכה יותר. כותרות חייבות להכיל לפחות %1 תווים.", diff --git a/public/language/he/global.json b/public/language/he/global.json index 2d1d55a783..6e76c19ea3 100644 --- a/public/language/he/global.json +++ b/public/language/he/global.json @@ -92,5 +92,6 @@ "enter_page_number": "הכנס מספר עמוד", "upload_file": "העלה קובץ", "upload": "העלה", - "allowed-file-types": "פורמטי הקבצים המורשים הם %1" + "allowed-file-types": "פורמטי הקבצים המורשים הם %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/hu/error.json b/public/language/hu/error.json index ce999c9ead..1718b8f115 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/hu/global.json b/public/language/hu/global.json index 116730ac61..b7f0edef28 100644 --- a/public/language/hu/global.json +++ b/public/language/hu/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/id/error.json b/public/language/id/error.json index 822698d462..cac1d43532 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/id/global.json b/public/language/id/global.json index 8ef7a1d220..aac14e74d7 100644 --- a/public/language/id/global.json +++ b/public/language/id/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/it/category.json b/public/language/it/category.json index 27c6ff7e94..bb6aef06dd 100644 --- a/public/language/it/category.json +++ b/public/language/it/category.json @@ -10,10 +10,10 @@ "share_this_category": "Condividi questa Categoria", "watch": "Osserva", "ignore": "Ignora", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", + "watching": "Seguito", + "ignoring": "Ignorato", + "watching.description": "Mostra discussione in non letti", + "ignoring.description": "Non mostrare discussione in non letti", "watch.message": "Non stai seguendo gli aggiornamenti di questa categoria", "ignore.message": "Da ora saranno ignorati gli aggiornamenti di questa categoria", "watched-categories": "Categorie osservate" diff --git a/public/language/it/error.json b/public/language/it/error.json index e2ff61bf88..23b0710512 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -14,13 +14,13 @@ "invalid-password": "Password non valida", "invalid-username-or-password": "Si prega di specificare sia un nome utente che una password", "invalid-search-term": "Termine di ricerca non valido", - "csrf-invalid": "We were unable to log you in, likely due to an expired session. Please try again", - "invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2", + "csrf-invalid": "Non siamo riusciti a farti connettere, probabilmente perché la sessione è scaduta. Per favore riprova.", + "invalid-pagination-value": "Valore di paginazione non valido, deve essere almeno %1 ed al massimo %2", "username-taken": "Nome utente già preso", "email-taken": "Email già esistente", "email-not-confirmed": "La tua Email deve essere ancora confermata, per favore clicca qui per confermare la tua Email.", "email-not-confirmed-chat": "Non potrai chattare finchè non avrai confermato la tua email, per favore clicca qui per farlo ora.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "La tua email non è ancora stata confermata, per favore controlla la tua casella di posta elettronica per l'email di conferma.", "no-email-to-confirm": "Questo forum richiede la conferma dell'indirizzo email, per favore clicca qui per inserirne uno", "email-confirm-failed": "Non possiamo confermare la tua email, per favore prova ancora più tardi.", "confirm-email-already-sent": "Email di conferma già inviata, per favore attendere %1 minuti per richiederne un'altra.", @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Inserisci un testo più lungo. Il messaggio deve contenere almeno %1 caratteri.", "content-too-long": "Inserisci un post più breve. I post non possono essere più lunghi di %1 caratteri.", "title-too-short": "Inserisci un titolo più lungo. I titoli devono contenere almeno %1 caratteri.", diff --git a/public/language/it/global.json b/public/language/it/global.json index e63dceae07..b20daeef18 100644 --- a/public/language/it/global.json +++ b/public/language/it/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ja/error.json b/public/language/ja/error.json index ba607d9c09..848d308257 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "より長く投稿を書いて下さい。投稿にはせめて%1文字が必要です。", "content-too-long": "より短く投稿を書いて下さい。投稿が%1文字以上が許されません。", "title-too-short": "より長くタイトルを書いて下さい。タイトルはせめて%1文字が必要です。", diff --git a/public/language/ja/global.json b/public/language/ja/global.json index 2d2ce15b92..1e89ac1a32 100644 --- a/public/language/ja/global.json +++ b/public/language/ja/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ko/error.json b/public/language/ko/error.json index 1a0e568eb3..e4e3686ae8 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "게시물의 내용이 너무 짧습니다. 내용은 최소 %1자 이상이어야 합니다.", "content-too-long": "게시물의 내용이 너무 깁니다. 내용은 최대 %1자 이내로 작성할 수 있습니다.", "title-too-short": "제목이 너무 짧습니다. 제목은 최소 %1자 이상이어야 합니다.", diff --git a/public/language/ko/global.json b/public/language/ko/global.json index b8c38121d1..e404ac910a 100644 --- a/public/language/ko/global.json +++ b/public/language/ko/global.json @@ -92,5 +92,6 @@ "enter_page_number": "페이지 번호를 입력하세요", "upload_file": "파일 업로드", "upload": "업로드", - "allowed-file-types": "사용가능한 파일 유형: %1" + "allowed-file-types": "사용가능한 파일 유형: %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/lt/error.json b/public/language/lt/error.json index a1c40b2542..33a845a2f3 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Prašome parašyti ilgesni pranešimą. Pranešimas turi sudaryti mažiausiai %1 simboli(us)", "content-too-long": "Prašome parašyti trumpesnį pranešimą. Pranešimas negali sudaryti daugiau %1 simboli(us)", "title-too-short": "Prašome įvesti ilgesni pavadinimą. Pavadinimas turi sudaryti mažiausiai %1 simboli(us)", diff --git a/public/language/lt/global.json b/public/language/lt/global.json index 3368f99fe2..3892b4ede8 100644 --- a/public/language/lt/global.json +++ b/public/language/lt/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ms/error.json b/public/language/ms/error.json index d7b0fed05c..64e13cdc36 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Sila masukkan kiriman yang lebih panjang. Kiriman mesti mengandungi sekurang-kurangnya %1 aksara().", "content-too-long": "Sila masukkan kiriman yang lebih ringkas. Kiriman mesti mengandungi tidak lebih %1 aksara().", "title-too-short": "Sila masukkan tajuk yang lebih panjang. Tajuk mesti mengandungi sekurang-kurangnya %1 aksara().", diff --git a/public/language/ms/global.json b/public/language/ms/global.json index 20bfd41623..e226d5f93c 100644 --- a/public/language/ms/global.json +++ b/public/language/ms/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/nb/error.json b/public/language/nb/error.json index b2df20d45c..b9756632e9 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Vennligst skriv et lengre innlegg. Innlegg må inneholde minst %1 tegn.", "content-too-long": "Vennligst skriv et kortere innlegg. Innlegg kan ikke være lengre enn %1 tegn.", "title-too-short": "Vennligst skriv en lengre tittel. Titler må inneholde minst %1 tegn.", diff --git a/public/language/nb/global.json b/public/language/nb/global.json index 9f24bcd29e..1e8af5dc12 100644 --- a/public/language/nb/global.json +++ b/public/language/nb/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/nl/error.json b/public/language/nl/error.json index c22b6ec22c..541de4e1ea 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Je kunt berichten pas %1 uur %2 minuten na het plaatsen verwijderen.", "post-delete-duration-expired-days": "Je kunt berichten pas %1 dagen na het plaatsen verwijderen.", "post-delete-duration-expired-days-hours": "Je kunt berichten pas %1 dag(en) %2 uur na het plaatsen verwijderen.", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Geef wat meer inhoud aan een bericht! Berichten dienen uit minimaal %1 teken(s) te bestaan.", "content-too-long": "Kort het bericht wat in, het aantal gebruikte tekens overschrijdt het ingestelde limiet want berichten mogen niet meer dan %1 teken(s) bevatten.", "title-too-short": "Geef een titel op die uit meer tekens bestaat. Titels dienen ten minste uit %1 teken(s) te bestaan.", diff --git a/public/language/nl/global.json b/public/language/nl/global.json index 95569088e6..c2cf89003a 100644 --- a/public/language/nl/global.json +++ b/public/language/nl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Voer paginanummer in", "upload_file": "Upload bestand", "upload": "Upload", - "allowed-file-types": "Toegestane bestandstypen zijn %1" + "allowed-file-types": "Toegestane bestandstypen zijn %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 0a887ccf89..c6618a2f59 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Możesz kasować posty przez %1 godzin(-y) i %2 minut(-y) po napisaniu", "post-delete-duration-expired-days": "Możesz kasować posty przez %1 dni po napisaniu", "post-delete-duration-expired-days-hours": "Możesz kasować posty przez %1 dni i %2 godzin(-y) po napisaniu", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Prosimy wpisać dłuższy post. Posty powinny zawierać co najmniej %1 znaków.", "content-too-long": "Prosimy wpisać krótszy post. Posty nie mogą zawierać więcej niż %1 znaków.", "title-too-short": "Prosimy podać dłuższy tytuł. Tytuły powinny zawierać co najmniej %1 znaków.", diff --git a/public/language/pl/global.json b/public/language/pl/global.json index 946993295c..f3a25ee6b2 100644 --- a/public/language/pl/global.json +++ b/public/language/pl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Wpisz numer strony", "upload_file": "Załaduj plik", "upload": "Załaduj", - "allowed-file-types": "Dozwolone typy plików %1" + "allowed-file-types": "Dozwolone typy plików %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index 64286e8352..780e0b5ec0 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Você só pode deletar posts por %1 hora(s) e %2 minutos(s) depois de postar", "post-delete-duration-expired-days": "Você só pode deletar posts por %1 dia(s) depois de postar", "post-delete-duration-expired-days-hours": "Você só pode deletar posts por %1 dia(s) e %2 hora(s) depois de postar", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Por favor digite um post maior. Posts precisam conter ao menos %1 caractere(s).", "content-too-long": "Por favor digite um post mais curto. Posts não podem ser maiores que %1 caractere(s)", "title-too-short": "Por favor digite um título maior. Títulos devem conter no mínimo %1 caractere(s)", diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json index 115d36e0b3..69c907a516 100644 --- a/public/language/pt_BR/global.json +++ b/public/language/pt_BR/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Digite o número da página", "upload_file": "Fazer upload de arquivo", "upload": "Upload", - "allowed-file-types": "Os tipos de arquivo permitidos são %1" + "allowed-file-types": "Os tipos de arquivo permitidos são %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ro/error.json b/public/language/ro/error.json index fdb92e303b..c0dc6a64a1 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/ro/global.json b/public/language/ro/global.json index 3b8a7bed12..7d2f15e71c 100644 --- a/public/language/ro/global.json +++ b/public/language/ro/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Introdu numărul paginei", "upload_file": "Încărcați fișierul", "upload": "Încărcați", - "allowed-file-types": "Tipuri de fișiere permise sunt %1" + "allowed-file-types": "Tipuri de fișiere permise sunt %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/ru/error.json b/public/language/ru/error.json index a0e7ea3108..ba5f39bd70 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Слишком короткое сообщение. Минимум символов: %1.", "content-too-long": "Слишком длинное сообщение. Максимум символов: %1.", "title-too-short": "Слишком короткое сообщение. Минимум символов: %1.", diff --git a/public/language/ru/global.json b/public/language/ru/global.json index 291cc69983..d5ed6dc2b9 100644 --- a/public/language/ru/global.json +++ b/public/language/ru/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Введите номер страницы", "upload_file": "Загрузить файл", "upload": "Загрузить", - "allowed-file-types": "Разрешенные форматы файлов %1" + "allowed-file-types": "Разрешенные форматы файлов %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 07e0a50743..3a24f2dd50 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Gerageza ushyireho ikintu kirekireho. Icyo ushyiraho kigomba kuba kigizwe nibura n'inyuguti (cyangwa ibimenyetso) zigera kuri %1.", "content-too-long": "Gerageza ushyireho ibintu bigufiyaho. Icyo ushyiraho kigomba kuba kigizwe n'inyuguti (cyangwa ibimenyetso) zirenga %1. ", "title-too-short": "Gerageza ushyireho umutwe muremureho. Umutwe ugomba kuba ugizwe n'inyuguti (cyangwa ibimenyetso) zigera kuri %1. ", diff --git a/public/language/rw/global.json b/public/language/rw/global.json index cc17af95c4..1f82d7cdfd 100644 --- a/public/language/rw/global.json +++ b/public/language/rw/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Shyiramo nimero ya paji", "upload_file": "Pakira ifayilo", "upload": "Pakira", - "allowed-file-types": "Ubwoko bw'amafayilo bwemewe ni %1" + "allowed-file-types": "Ubwoko bw'amafayilo bwemewe ni %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 6a614b6de4..2ec9878ee8 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/sc/global.json b/public/language/sc/global.json index 61ba71763c..9f7f952155 100644 --- a/public/language/sc/global.json +++ b/public/language/sc/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sk/error.json b/public/language/sk/error.json index d5116a21d9..9788c91cbe 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/sk/global.json b/public/language/sk/global.json index 5772ec7ef2..14cc91fb07 100644 --- a/public/language/sk/global.json +++ b/public/language/sk/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sl/error.json b/public/language/sl/error.json index 709870ec9c..d9a7b503af 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Prosimo napišite daljšo objavo. Objave morajo vsebovati vsaj %1 znak(ov).", "content-too-long": "Prosimo napišite krajšo objavo. Objave ne smejo vsebovati več kot %1 znak(ov).", "title-too-short": "Prosimo vnesite daljši naslov. Naslovi morajo vsebovati vsaj %1 znak(ov).", diff --git a/public/language/sl/global.json b/public/language/sl/global.json index 5dd5474601..911fb6a7ff 100644 --- a/public/language/sl/global.json +++ b/public/language/sl/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 05241438be..d32aa4364b 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/sr/global.json b/public/language/sr/global.json index 556cdb029f..97e71581ea 100644 --- a/public/language/sr/global.json +++ b/public/language/sr/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 172f78e6cd..aecff9870f 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Du kan endast radera inlägg inom %1 timmar(er) %2 minut(er) efter att ha skickat det", "post-delete-duration-expired-days": "Du kan endast radera inlägg inom %1 dag(ar) efter att ha skickat det", "post-delete-duration-expired-days-hours": "Du kan endast radera inlägg inom %1 dag(ar) %2 timm(ar) efter att ha skickat det", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Skriv ett längre inlägg. Inlägg måste innehålla minst %1 tecken.", "content-too-long": "Skriv ett kortare inlägg. Inlägg kan inte innehålla mer än %1 tecken.", "title-too-short": "Skriv en längre rubrik. Rubriker måste innehålla minst %1 tecken.", diff --git a/public/language/sv/global.json b/public/language/sv/global.json index c175e75cd7..5c1b98522e 100644 --- a/public/language/sv/global.json +++ b/public/language/sv/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Skriv in sidnummer", "upload_file": "Ladda upp en fil", "upload": "Ladda upp", - "allowed-file-types": "Tillåtna filtyper är %1" + "allowed-file-types": "Tillåtna filtyper är %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/th/error.json b/public/language/th/error.json index 27373e3aa5..45d5070642 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).", "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).", "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).", diff --git a/public/language/th/global.json b/public/language/th/global.json index b280237529..eb81fb4ba5 100644 --- a/public/language/th/global.json +++ b/public/language/th/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 48811f7373..5e8619faf2 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Gönderildikten %1 saat(s) %2 dakika(s) sonra iletini silmene izin verilir.", "post-delete-duration-expired-days": "Gönderildikten %1 gün(s) sonra iletini silmene izin verilir.", "post-delete-duration-expired-days-hours": "Gönderildikten %1 gün(s) %2 saat(s) sonra iletini silmene izin verilir.", + "cant-delete-topic-has-reply": "Bir ileti varken başlığı silemezsiniz", + "cant-delete-topic-has-replies": "Başlığına %1 ileti girildikten sonra silemezsin", "content-too-short": "Lütfen daha uzun bir ileti girin. En az %1 karakter.", "content-too-long": "Lütfen daha kısa bir ileti girin. İletiler %1 karakterden uzun olamaz.", "title-too-short": "Lütfen daha uzun bir başlık girin. Başlıklar en az %1 karakter içermelidir.", diff --git a/public/language/tr/global.json b/public/language/tr/global.json index 0e5c44add6..9e3c328dae 100644 --- a/public/language/tr/global.json +++ b/public/language/tr/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Sayfa numarasını girin", "upload_file": "Dosya yükle", "upload": "Yükle", - "allowed-file-types": "İzin verilen dosya tipleri %1" + "allowed-file-types": "İzin verilen dosya tipleri %1", + "unsaved-changes": "Kaydedilmemiş değişiklikler var. Çıkmak istediğinize emin misiniz?" } \ No newline at end of file diff --git a/public/language/tr/login.json b/public/language/tr/login.json index 9bf22ba985..5db8629859 100644 --- a/public/language/tr/login.json +++ b/public/language/tr/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Giriş Başarısız", "login_successful": "Başarıyla giriş yaptınız!", "dont_have_account": "Hesabınız yok mu?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Hareketsizlik nedeniyle yönetici panelinden çıkış yapıldı" } \ No newline at end of file diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 6af283b8de..54f4e7d5c2 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giờ(s) 2 phút(s)", "post-delete-duration-expired-days": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s)", "post-delete-duration-expired-days-hours": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s) %2 giờ(s)", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "Vui lòng nhập một bài viết dài hơn. Bài viết phải có tối thiểu %1 ký tự.", "content-too-long": "Vui lòng nhập một bài viết ngắn hơn. Bài viết chỉ có thể có tối đa %1 ký tự.", "title-too-short": "Vui lòng nhập tiêu đề dài hơn. Tiêu đề phải có tối thiểu %1 ký tự.", diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 6928ecfefa..2cbb1536c8 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -92,5 +92,6 @@ "enter_page_number": "Nhập vào số trang", "upload_file": "Tải file lên", "upload": "Tải lên", - "allowed-file-types": "Các định dạng file được cho phép là %1" + "allowed-file-types": "Các định dạng file được cho phép là %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index aa496cca6f..5788c57c59 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "您只能在发表 %1 小时 %2 分钟后删除帖子", "post-delete-duration-expired-days": "您只能在发表 %1 天后删除帖子", "post-delete-duration-expired-days-hours": "您只能在发表 %1 天 %2 小时后删除帖子", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "请增添发帖内容,不能少于 %1 个字符。", "content-too-long": "请删减发帖内容,不能超过 %1 个字符。", "title-too-short": "请增加标题,不能少于 %1 个字符。", diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json index ca3c5d43f2..4be989cb9f 100644 --- a/public/language/zh_CN/global.json +++ b/public/language/zh_CN/global.json @@ -92,5 +92,6 @@ "enter_page_number": "输入页码", "upload_file": "上传文件", "upload": "上传", - "allowed-file-types": "允许的文件类型有 %1" + "allowed-file-types": "允许的文件类型有 %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index 3c9a9849e6..3beacd4374 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -55,6 +55,8 @@ "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", + "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", "content-too-short": "請輸入一個長一點的張貼內容。張貼內容長度不能少於 %1 字元。", "content-too-long": "請輸入一個短一點的張貼內容。張貼內容長度不能超過 %1 字元。", "title-too-short": "請輸入一個長一點的標題。標題長度不能少於 %1 字元。", diff --git a/public/language/zh_TW/global.json b/public/language/zh_TW/global.json index 4533cbfc62..d530c0ace1 100644 --- a/public/language/zh_TW/global.json +++ b/public/language/zh_TW/global.json @@ -92,5 +92,6 @@ "enter_page_number": "輸入頁碼", "upload_file": "上傳檔案", "upload": "上傳", - "allowed-file-types": "允許的檔案類型是 %1" + "allowed-file-types": "允許的檔案類型是 %1", + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } \ No newline at end of file diff --git a/public/less/generics.less b/public/less/generics.less index bca1b45649..d2be83d01e 100644 --- a/public/less/generics.less +++ b/public/less/generics.less @@ -94,6 +94,12 @@ .user-icon-style(24px, 1.5rem); } + &.avatar-md { + width: 32px; + height: 32px; + .user-icon-style(32px, 1.5rem); + } + &.avatar-lg { width: 128px; height: 128px; diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index a90967c2e0..add13c30cc 100644 --- a/public/src/admin/admin.js +++ b/public/src/admin/admin.js @@ -82,7 +82,7 @@ socket.emit('admin.restart'); }); - mousetrap.bind('/', function(e) { + mousetrap.bind('/', function(event) { $('#acp-search input').focus(); return false; diff --git a/public/src/admin/advanced/errors.js b/public/src/admin/advanced/errors.js index 52d6427240..3977778b5a 100644 --- a/public/src/admin/advanced/errors.js +++ b/public/src/admin/advanced/errors.js @@ -14,6 +14,10 @@ define('admin/advanced/errors', ['Chart'], function(Chart) { bootbox.confirm('Are you sure you wish to clear the 404 error logs?', function(ok) { if (ok) { socket.emit('admin.errors.clear', {}, function(err) { + if (err) { + return app.alertError(err.message); + } + ajaxify.refresh(); app.alertSuccess('"404 Not Found" errors cleared'); }); diff --git a/public/src/admin/advanced/events.js b/public/src/admin/advanced/events.js index 9075947d60..a952666786 100644 --- a/public/src/admin/advanced/events.js +++ b/public/src/admin/advanced/events.js @@ -1,8 +1,9 @@ "use strict"; -/* global define, socket, app, templates */ + +/* global define, socket, app */ -define('admin/advanced/events', ['forum/infinitescroll'], function(infinitescroll) { +define('admin/advanced/events', function() { var Events = {}; Events.init = function() { @@ -16,25 +17,6 @@ define('admin/advanced/events', ['forum/infinitescroll'], function(infinitescrol }); }); - infinitescroll.init(function(direction) { - if (direction < 0 || !$('.events').length) { - return; - } - - infinitescroll.loadMore('admin.getMoreEvents', $('[data-next]').attr('data-next'), function(data, done) { - if (data.events && data.events.length) { - templates.parse('admin/advanced/events', 'events', {events: data.events}, function(html) { - $('.events-list').append(html); - done(); - }); - - $('[data-next]').attr('data-next', data.next); - } else { - done(); - } - }); - }); - }; return Events; diff --git a/public/src/admin/advanced/logs.js b/public/src/admin/advanced/logs.js index ea0503d8a6..8411effabc 100644 --- a/public/src/admin/advanced/logs.js +++ b/public/src/admin/advanced/logs.js @@ -10,7 +10,7 @@ define('admin/advanced/logs', function() { // Affix menu $('.affix').affix(); - $('.logs').find('button[data-action]').on('click', function(e) { + $('.logs').find('button[data-action]').on('click', function(event) { var btnEl = $(this), action = btnEl.attr('data-action'); diff --git a/public/src/admin/appearance/customise.js b/public/src/admin/appearance/customise.js index a85fd663ad..e76bb9d3e4 100644 --- a/public/src/admin/appearance/customise.js +++ b/public/src/admin/appearance/customise.js @@ -15,7 +15,7 @@ define('admin/appearance/customise', ['admin/settings'], function(Settings) { customCSS.setTheme("ace/theme/twilight"); customCSS.getSession().setMode("ace/mode/css"); - customCSS.on('change', function(e) { + customCSS.on('change', function(event) { app.flags = app.flags || {}; app.flags._unsaved = true; $('#customCSS-holder').val(customCSS.getValue()); @@ -24,7 +24,7 @@ define('admin/appearance/customise', ['admin/settings'], function(Settings) { customHTML.setTheme("ace/theme/twilight"); customHTML.getSession().setMode("ace/mode/html"); - customHTML.on('change', function(e) { + customHTML.on('change', function(event) { app.flags = app.flags || {}; app.flags._unsaved = true; $('#customHTML-holder').val(customHTML.getValue()); diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js index b4e8767184..eccb94f527 100644 --- a/public/src/admin/extend/plugins.js +++ b/public/src/admin/extend/plugins.js @@ -20,6 +20,9 @@ define('admin/extend/plugins', function() { pluginID = pluginEl.attr('data-plugin-id'); var btn = $('#' + pluginID + ' [data-action="toggleActive"]'); socket.emit('admin.plugins.toggleActive', pluginID, function(err, status) { + if (err) { + return app.alertError(err); + } btn.html(' ' + (status.active ? 'Deactivate' : 'Activate')); btn.toggleClass('btn-warning', status.active).toggleClass('btn-success', !status.active); diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js index 1224d2ace2..f32cb2d839 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -306,6 +306,9 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { units: units || 'hours', until: until }, function (err, data) { + if (err) { + return app.alertError(err.message); + } if (JSON.stringify(graphData.traffic) === JSON.stringify(data)) { return; } diff --git a/public/src/admin/manage/groups.js b/public/src/admin/manage/groups.js index 8c68837403..3eee1ca08c 100644 --- a/public/src/admin/manage/groups.js +++ b/public/src/admin/manage/groups.js @@ -95,6 +95,10 @@ define('admin/manage/groups', [ sort: 'date' } }, function(err, groups) { + if (err) { + return app.alertError(err.message); + } + templates.parse('admin/manage/groups', 'groups', { groups: groups }, function(html) { diff --git a/public/src/admin/manage/ip-blacklist.js b/public/src/admin/manage/ip-blacklist.js index 9769b3e3bb..949d8ac8fd 100644 --- a/public/src/admin/manage/ip-blacklist.js +++ b/public/src/admin/manage/ip-blacklist.js @@ -29,6 +29,10 @@ define('admin/manage/ip-blacklist', [], function() { socket.emit('blacklist.validate', { rules: blacklist.val() }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + templates.parse('admin/partials/blacklist-validate', data, function(html) { bootbox.alert(html); }); diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index 01f755612b..9fda81eccb 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -1,5 +1,5 @@ "use strict"; -/*global define, socket, app, utils, bootbox*/ +/*global define, socket, app, utils, bootbox, ajaxify*/ define('admin/manage/tags', [ 'forum/infinitescroll', @@ -12,11 +12,47 @@ define('admin/manage/tags', [ Tags.init = function() { selectable.enable('.tag-management', '.tag-row'); + handleCreate(); handleSearch(); handleModify(); handleDeleteSelected(); }; + function handleCreate() { + var createModal = $('#create-modal'); + var createTagName = $('#create-tag-name'); + var createModalGo = $('#create-modal-go'); + + createModal.on('keypress', function(e) { + if (e.keyCode === 13) { + createModalGo.click(); + } + }); + + $('#create').on('click', function() { + createModal.modal('show'); + setTimeout(function() { + createTagName.focus(); + }, 250); + }); + + createModalGo.on('click', function() { + socket.emit('admin.tags.create', { + tag: createTagName.val() + }, function(err) { + if (err) { + return app.alertError(err.message); + } + + createTagName.val(''); + createModal.on('hidden.bs.modal', function() { + ajaxify.refresh(); + }); + createModal.modal('hide'); + }); + }); + } + function handleSearch() { $('#tag-search').on('input propertychange', function() { if (timeoutId) { diff --git a/public/src/admin/settings/email.js b/public/src/admin/settings/email.js index a63546f0ba..e6015b3b32 100644 --- a/public/src/admin/settings/email.js +++ b/public/src/admin/settings/email.js @@ -29,7 +29,7 @@ define('admin/settings/email', ['admin/settings'], function(settings) { emailEditor.setTheme("ace/theme/twilight"); emailEditor.getSession().setMode("ace/mode/html"); - emailEditor.on('change', function(e) { + emailEditor.on('change', function(event) { $('#email-editor-holder').val(emailEditor.getValue()); }); @@ -58,4 +58,4 @@ define('admin/settings/email', ['admin/settings'], function(settings) { } return module; -}); \ No newline at end of file +}); diff --git a/public/src/app.js b/public/src/app.js index a913517363..66257e7980 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -40,7 +40,7 @@ app.cacheBuster = null; components.get('user/logout').on('click', app.logout); }); - Visibility.change(function(e, state){ + Visibility.change(function(event, state){ if (state === 'visible') { app.isFocused = true; app.alternatingTitle(''); @@ -117,6 +117,10 @@ app.cacheBuster = null; }; app.alertError = function (message, timeout) { + if (message === '[[error:invalid-session]]') { + return app.handleInvalidSession(); + } + app.alert({ title: '[[global:alert.error]]', message: message, @@ -125,6 +129,28 @@ app.cacheBuster = null; }); }; + app.handleInvalidSession = function() { + if (app.flags && app.flags._sessionRefresh) { + return; + } + + app.flags = app.flags || {}; + app.flags._sessionRefresh = true; + + require(['translator'], function(translator) { + translator.translate('[[error:invalid-session-text]]', function(translated) { + bootbox.alert({ + title: '[[error:invalid-session]]', + message: translated, + closeButton: false, + callback: function() { + window.location.reload(); + } + }); + }); + }); + }; + app.enterRoom = function (room, callback) { callback = callback || function() {}; if (socket && app.user.uid && app.currentRoom !== room) { @@ -261,7 +287,8 @@ app.cacheBuster = null; }); }; - app.newChat = function (touid) { + app.newChat = function (touid, callback) { + callback = callback || function() {}; if (!app.user.uid) { return app.alertError('[[error:not-logged-in]]'); } @@ -270,11 +297,14 @@ app.cacheBuster = null; if (err) { return app.alertError(err.message); } + if (!ajaxify.currentPage.startsWith('chats')) { app.openChat(roomId); } else { ajaxify.go('chats/' + roomId); } + + callback(false, roomId); }); }; @@ -409,7 +439,9 @@ app.cacheBuster = null; $('#search-form').on('submit', function () { var input = $(this).find('input'); require(['search'], function(search) { - search.query({term: input.val()}, function() { + var data = search.getSearchPreferences(); + data.term = input.val(); + search.query(data, function() { input.val(''); }); }); diff --git a/public/src/client/category.js b/public/src/client/category.js index 98d0063b4b..af0d26b980 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -91,6 +91,10 @@ define('forum/category', [ Category.toBottom = function() { socket.emit('categories.getTopicCount', ajaxify.data.cid, function(err, count) { + if (err) { + return app.alertError(err.message); + } + navigator.scrollBottom(count - 1); }); }; diff --git a/public/src/client/categoryTools.js b/public/src/client/categoryTools.js index 348ff684f8..970ab29d4a 100644 --- a/public/src/client/categoryTools.js +++ b/public/src/client/categoryTools.js @@ -87,6 +87,10 @@ define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', components.get('topic/move-all').on('click', function() { move.init(null, cid, function(err) { + if (err) { + return app.alertError(err.message); + } + ajaxify.refresh(); }); }); diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index c95120ef77..f71b37886e 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -230,7 +230,7 @@ define('forum/groups/details', [ if (ajaxify.data.group.isOwner) { var searchInput = $('[component="groups/members/invite"]'); require(['autocomplete'], function(autocomplete) { - autocomplete.user(searchInput, function(e, selected) { + autocomplete.user(searchInput, function(event, selected) { socket.emit('groups.issueInvite', { toUid: selected.item.user.uid, groupName: ajaxify.data.group.name @@ -259,4 +259,4 @@ define('forum/groups/details', [ } return Details; -}); \ No newline at end of file +}); diff --git a/public/src/client/search.js b/public/src/client/search.js index 6ca5b8dc41..bf07ad70b6 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -1,6 +1,6 @@ 'use strict'; -/* globals app, define, utils, socket*/ +/* globals app, define, utils*/ define('forum/search', ['search', 'autocomplete'], function(searchModule, autocomplete) { var Search = {}; @@ -67,7 +67,7 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco function fillOutForm() { var params = utils.params(); - var searchData = getSearchPreferences(); + var searchData = searchModule.getSearchPreferences(); params = utils.merge(searchData, params); if (params) { @@ -156,14 +156,6 @@ define('forum/search', ['search', 'autocomplete'], function(searchModule, autoco }); } - function getSearchPreferences() { - try { - return JSON.parse(localStorage.getItem('search-preferences')); - } catch(e) { - return {}; - } - } - function enableAutoComplete() { autocomplete.user($('#posted-by-user')); } diff --git a/public/src/client/topic.js b/public/src/client/topic.js index aff00909d1..dcff9d3fe1 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -127,6 +127,9 @@ define('forum/topic', [ Topic.toBottom = function() { socket.emit('topics.postcount', ajaxify.data.tid, function(err, postCount) { + if (err) { + return app.alertError(err.message); + } if (config.topicPostSort !== 'oldest_to_newest') { postCount = 2; } @@ -198,7 +201,8 @@ define('forum/topic', [ var toPost = $('[component="post"][data-pid="' + toPid + '"]'); if (toPost.length) { e.preventDefault(); - return navigator.scrollToPost(toPost.attr('data-index'), true); + navigator.scrollToPost(toPost.attr('data-index'), true); + return false; } }); } diff --git a/public/src/client/users.js b/public/src/client/users.js index fe083a13e2..40f221e33f 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -47,6 +47,10 @@ define('forum/users', ['translator'], function(translator) { set: set, after: after }, function(err, data) { + if (err) { + return app.alertError(err.message); + } + if (data && data.users.length) { onUsersLoaded(data); $('#load-more-users-btn').removeClass('disabled'); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index f2ff94bd55..445b4212e4 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -26,6 +26,15 @@ define('chat', [ module.loadChatsDropdown(chatsListEl); }); + chatsListEl.on('click', '[data-roomid]', function() { + var roomId = this.getAttribute('data-roomid'); + if (!ajaxify.currentPage.match(/^chats\//)) { + app.openChat(roomId); + } else { + ajaxify.go('chats/' + roomId); + } + }); + $('[component="chats/mark-all-read"]').on('click', function() { socket.emit('modules.chats.markAllRead', function(err) { if (err) { @@ -106,44 +115,11 @@ define('chat', [ chatsListEl.empty(); - if (!rooms.length) { - translator.translate('[[modules:chat.no_active]]', function(str) { - $('
  • ') - .addClass('no_active') - .html('' + str + '') - .appendTo(chatsListEl); - }); - return; - } - - rooms.forEach(function(roomObj) { - function createUserImage(userObj) { - return '' + - (userObj.picture ? - '' : - '
    ' + userObj['icon:text'] + '
    ') + - ' ' + - roomObj.usernames + '
    '; - } - - var dropdownEl = $('
  • ') - .attr('data-roomId', roomObj.roomId) - .appendTo(chatsListEl); - - if (roomObj.lastUser) { - dropdownEl.append(createUserImage(roomObj.lastUser)); - } else { - translator.translate('[[modules:chat.no-users-in-room]]', function(str) { - dropdownEl.append(str); - }); - } - - dropdownEl.click(function() { - if (!ajaxify.currentPage.match(/^chats\//)) { - app.openChat(roomObj.roomId); - } else { - ajaxify.go('chats/' + roomObj.roomId); - } + templates.parse('partials/chat_dropdown', { + rooms: rooms + }, function(html) { + translator.translate(html, function(translated) { + chatsListEl.html(translated); }); }); }); diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 533199c31c..dcabe7d0a1 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -103,6 +103,10 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound, } socket.emit('notifications.getCount', function(err, count) { + if (err) { + return app.alertError(err.message); + } + Notifications.updateNotifCount(count); }); diff --git a/public/src/modules/search.js b/public/src/modules/search.js index e24ecbed61..be6ac6a711 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -73,6 +73,14 @@ define('search', ['navigator', 'translator'], function(nav, translator) { return decodeURIComponent($.param(query)); } + Search.getSearchPreferences = function() { + try { + return JSON.parse(localStorage.getItem('search-preferences')); + } catch(e) { + return {}; + } + }; + Search.queryTopic = function(tid, term, callback) { socket.emit('topics.search', { tid: tid, @@ -134,6 +142,10 @@ define('search', ['navigator', 'translator'], function(nav, translator) { topicPostSort: config.topicPostSort }; socket.emit('posts.getPidIndex', data, function(err, postIndex) { + if (err) { + return app.alertError(err.message); + } + nav.scrollToPost(postIndex, true); }); } else { diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 79c150854d..692e0ec7b4 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -508,13 +508,21 @@ define('settings', function () { app.flags._unsaved = false; if (typeof callback === 'function') { - callback(); + callback(err); } else { - app.alert({ - title: 'Settings Saved', - type: 'success', - timeout: 2500 - }); + if (err) { + app.alert({ + title: 'Error while saving settings', + type: 'error', + timeout: 2500 + }); + } else { + app.alert({ + title: 'Settings Saved', + type: 'success', + timeout: 2500 + }); + } } }); } diff --git a/public/src/sockets.js b/public/src/sockets.js index 461c148be7..f2a9bfd346 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -28,6 +28,12 @@ app.isConnected = false; setTimeout(socket.connect.bind(socket), parseInt(config.reconnectionDelay, 10) * 10); }); + socket.on('checkSession', function(uid) { + if (parseInt(uid, 10) !== parseInt(app.user.uid, 10)) { + app.handleInvalidSession(); + } + }); + socket.on('event:banned', onEventBanned); socket.on('event:alert', app.alert); diff --git a/public/src/utils.js b/public/src/utils.js index a1ce6959a4..5d44bcd4fa 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -45,8 +45,16 @@ list.forEach(function(file) { file = dir + '/' + file; fs.stat(file, function(err, stat) { + if (err) { + return done(err); + } + if (stat && stat.isDirectory()) { utils.walk(file, function(err, res) { + if (err) { + return done(err); + } + results = results.concat(res); if (!--pending) { done(null, results); diff --git a/src/controllers/accounts/info.js b/src/controllers/accounts/info.js index 1117cb28c1..b39c82f939 100644 --- a/src/controllers/accounts/info.js +++ b/src/controllers/accounts/info.js @@ -18,7 +18,8 @@ infoController.get = function(req, res, next) { async.parallel({ ips: async.apply(user.getIPs, res.locals.uid, 4), history: async.apply(user.getModerationHistory, res.locals.uid), - fields: async.apply(user.getUserFields, res.locals.uid, ['banned']) + fields: async.apply(user.getUserFields, res.locals.uid, ['banned']), + sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID) }, function(err, data) { if (err) { return next(err); @@ -28,9 +29,10 @@ infoController.get = function(req, res, next) { ips: data.ips, history: data.history }, data.fields); - + + userData.sessions = data.sessions; userData.title = '[[pages:account/info]]'; - userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:settings]]'}]); + userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:account_info]]'}]); res.render('account/info', userData); }); diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index 9efa8ebe6b..3b169bf83c 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -37,19 +37,13 @@ settingsController.get = function(req, res, callback) { }, homePageRoutes: function(next) { getHomePageRoutes(next); - }, - ips: function (next) { - user.getIPs(userData.uid, 4, next); - }, - sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID) + } }, next); }, function(results, next) { userData.settings = results.settings; userData.languages = results.languages; userData.homePageRoutes = results.homePageRoutes; - userData.ips = results.ips; - userData.sessions = results.sessions; plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next); }, function(data, next) { diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js index 04001e0f65..c33dde5c7c 100644 --- a/src/controllers/admin/categories.js +++ b/src/controllers/admin/categories.js @@ -48,6 +48,10 @@ categoriesController.getAnalytics = function(req, res, next) { name: async.apply(categories.getCategoryField, req.params.category_id, 'name'), analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id) }, function(err, data) { + if (err) { + return next(err); + } + res.render('admin/manage/category-analytics', data); }); }; diff --git a/src/controllers/admin/errors.js b/src/controllers/admin/errors.js index 4b59932965..d2e29d5eb1 100644 --- a/src/controllers/admin/errors.js +++ b/src/controllers/admin/errors.js @@ -8,20 +8,28 @@ var meta = require('../../meta'), var errorsController = {}; -errorsController.get = function(req, res) { +errorsController.get = function(req, res, next) { async.parallel({ 'not-found': async.apply(meta.errors.get, true), analytics: async.apply(analytics.getErrorAnalytics) }, function(err, data) { + if (err) { + return next(err); + } + res.render('admin/advanced/errors', data); }); }; -errorsController.export = function(req, res) { +errorsController.export = function(req, res, next) { async.waterfall([ async.apply(meta.errors.get, false), async.apply(json2csv) ], function(err, csv) { + if (err) { + return next(err); + } + res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="404.csv"').send(csv); }); }; diff --git a/src/controllers/admin/events.js b/src/controllers/admin/events.js index ceee1e2a70..0e431d3d4e 100644 --- a/src/controllers/admin/events.js +++ b/src/controllers/admin/events.js @@ -1,18 +1,38 @@ 'use strict'; +var async = require('async'); + +var db = require('../../database'); var events = require('../../events'); +var pagination = require('../../pagination'); var eventsController = {}; eventsController.get = function(req, res, next) { - events.getEvents(0, 19, function(err, events) { + + var page = parseInt(req.query.page, 10) || 1; + var itemsPerPage = 20; + var start = (page - 1) * 20; + var stop = start + itemsPerPage - 1; + + async.parallel({ + eventCount: function(next) { + db.sortedSetCard('events:time', next); + }, + events: function(next) { + events.getEvents(start, stop, next); + } + }, function(err, results) { if (err) { return next(err); } + var pageCount = Math.max(1, Math.ceil(results.eventCount / itemsPerPage)); + res.render('admin/advanced/events', { - events: events, + events: results.events, + pagination: pagination.create(page, pageCount), next: 20 }); }); diff --git a/src/controllers/admin/homepage.js b/src/controllers/admin/homepage.js index f503dd8865..89ef0db928 100644 --- a/src/controllers/admin/homepage.js +++ b/src/controllers/admin/homepage.js @@ -49,6 +49,10 @@ homePageController.get = function(req, res, next) { name: 'Popular' } ].concat(categoryData)}, function(err, data) { + if (err) { + return next(err); + } + data.routes.push({ route: '', name: 'Custom' diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js index bf0975058a..50c44ef1b7 100644 --- a/src/controllers/admin/settings.js +++ b/src/controllers/admin/settings.js @@ -25,13 +25,21 @@ function renderEmail(req, res, next) { var emailsPath = path.join(__dirname, '../../../public/templates/emails'); utils.walk(emailsPath, function(err, emails) { + if (err) { + return next(err); + } + async.map(emails, function(email, next) { var path = email.replace(emailsPath, '').substr(1).replace('.tpl', ''); fs.readFile(email, function(err, original) { + if (err) { + return next(err); + } + var text = meta.config['email:custom:' + path] ? meta.config['email:custom:' + path] : original.toString(); - next(err, { + next(null, { path: path, fullpath: email, text: text, @@ -39,6 +47,10 @@ function renderEmail(req, res, next) { }); }); }, function(err, emails) { + if (err) { + return next(err); + } + res.render('admin/settings/email', { emails: emails, sendable: emails.filter(function(email) { diff --git a/src/controllers/admin/sounds.js b/src/controllers/admin/sounds.js index 6e7ebf3f19..b583125c5a 100644 --- a/src/controllers/admin/sounds.js +++ b/src/controllers/admin/sounds.js @@ -6,6 +6,10 @@ var soundsController = {}; soundsController.get = function(req, res, next) { meta.sounds.getFiles(function(err, sounds) { + if (err) { + return next(err); + } + sounds = Object.keys(sounds).map(function(name) { return { name: name diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 90dae8a243..b507e092cf 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -61,6 +61,10 @@ uploadsController.uploadTouchIcon = function(req, res, next) { if (validateUpload(req, res, next, uploadedFile, allowedTypes)) { file.saveFileToLocal('touchicon-orig.png', 'system', uploadedFile.path, function(err, imageObj) { + if (err) { + return next(err); + } + // Resize the image into squares for use as touch icons at various DPIs async.each(sizes, function(size, next) { async.series([ diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index e682045fce..bbfa71df95 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -5,6 +5,7 @@ var user = require('../../user'); var meta = require('../../meta'); var db = require('../../database'); var pagination = require('../../pagination'); +var events = require('../../events'); var usersController = {}; @@ -180,6 +181,12 @@ function render(req, res, data) { } usersController.getCSV = function(req, res, next) { + events.log({ + type: 'getUsersCSV', + uid: req.user.uid, + ip: req.ip + }); + user.getUsersCSV(function(err, data) { if (err) { return next(err); diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index bc301b1873..98dc6c1fe4 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -96,6 +96,10 @@ function registerAndLoginUser(req, res, userData, callback) { userData: userData, interstitials: [] }, function(err, data) { + if (err) { + return next(err); + } + // If interstitials are found, save registration attempt into session and abort var deferRegistration = data.interstitials.length; @@ -144,6 +148,10 @@ authenticationController.registerComplete = function(req, res, next) { userData: req.session.registration, interstitials: [] }, function(err, data) { + if (err) { + return next(err); + } + var callbacks = data.interstitials.reduce(function(memo, cur) { if (cur.hasOwnProperty('callback') && typeof cur.callback === 'function') { memo.push(async.apply(cur.callback, req.session.registration, req.body)); @@ -243,6 +251,10 @@ function continueLogin(req, res, next) { winston.verbose('[auth] Triggering password reset for uid ' + userData.uid + ' due to password policy'); req.session.passwordExpired = true; user.reset.generate(userData.uid, function(err, code) { + if (err) { + return res.status(403).send(err.message); + } + res.status(200).send(nconf.get('relative_path') + '/reset/' + code); }); } else { diff --git a/src/controllers/index.js b/src/controllers/index.js index 4ad3d5ab0a..47047924a4 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -202,6 +202,10 @@ Controllers.registerInterstitial = function(req, res, next) { userData: req.session.registration, interstitials: [] }, function(err, data) { + if (err) { + return next(err); + } + if (!data.interstitials.length) { return next(); } @@ -212,6 +216,10 @@ Controllers.registerInterstitial = function(req, res, next) { var errors = req.flash('error'); async.parallel(renders, function(err, sections) { + if (err) { + return next(err); + } + res.render('registerComplete', { errors: errors, sections: sections @@ -331,6 +339,10 @@ Controllers.termsOfUse = function(req, res, next) { res.render('tos', {termsOfUse: meta.config.termsOfUse}); }; +Controllers.ping = function(req, res) { + res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); +}; + Controllers.handle404 = function(req, res) { var relativePath = nconf.get('relative_path'); var isLanguage = new RegExp('^' + relativePath + '/language/.*/.*.json'); @@ -373,6 +385,35 @@ Controllers.handle404 = function(req, res) { } }; +Controllers.handleURIErrors = function(err, req, res, next) { + // Handle cases where malformed URIs are passed in + if (err instanceof URIError) { + var tidMatch = req.path.match(/^\/topic\/(\d+)\//); + var cidMatch = req.path.match(/^\/category\/(\d+)\//); + + if (tidMatch) { + res.redirect(nconf.get('relative_path') + tidMatch[0]); + } else if (cidMatch) { + res.redirect(nconf.get('relative_path') + cidMatch[0]); + } else { + winston.warn('[controller] Bad request: ' + req.path); + if (res.locals.isAPI) { + res.status(400).json({ + error: '[[global:400.title]]' + }); + } else { + req.app.locals.middleware.buildHeader(req, res, function() { + res.render('400', { error: validator.escape(String(err.message)) }); + }); + } + } + + return; + } else { + next(); + } +}; + Controllers.handleErrors = function(err, req, res, next) { switch (err.code) { case 'EBADCSRFTOKEN': @@ -395,7 +436,7 @@ Controllers.handleErrors = function(err, req, res, next) { res.json({path: validator.escape(path), error: err.message}); } else { req.app.locals.middleware.buildHeader(req, res, function() { - res.render('500', {path: validator.escape(path), error: validator.escape(String(err.message))}); + res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) }); }); } }; diff --git a/src/controllers/tags.js b/src/controllers/tags.js index 45ab3424aa..163efc2af0 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -44,7 +44,6 @@ tagsController.getTag = function(req, res, next) { }, function (results, next) { if (Array.isArray(results.tids) && !results.tids.length) { - topics.deleteTag(req.params.tag); return res.render('tag', templateData); } topicCount = results.topicCount; diff --git a/src/controllers/users.js b/src/controllers/users.js index 4ee7e5a9ec..20ea62ca40 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -102,6 +102,10 @@ usersController.getUsers = function(set, uid, page, callback) { 'users:flags': {title: '[[pages:users/most-flags]]', crumb: '[[users:most_flags]]'}, }; + if (!setToData[set]) { + setToData[set] = {title: '', crumb: ''}; + } + var breadcrumbs = [{text: setToData[set].crumb}]; if (set !== 'users:joindate') { diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 1f9a67adb1..5cc06eab42 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -1,6 +1,7 @@ "use strict"; var async = require('async'); +var utils = require('../../../public/src/utils'); module.exports = function(db, module) { var helpers = module.helpers.mongo; @@ -381,12 +382,12 @@ module.exports = function(db, module) { map[item.value] = item.score; }); - var returnData = new Array(values.length), - score; + var returnData = new Array(values.length); + var score; for(var i=0; i 2) { notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]'; } + + notifications[modifyIndex].path = set[set.length-1].path; break; case 'new_register': diff --git a/src/plugins.js b/src/plugins.js index 6eba5de2ce..c531dfa27d 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -94,6 +94,10 @@ var middleware; function(next) { // Build language code list fs.readdir(path.join(__dirname, '../public/language'), function(err, directories) { + if (err) { + return next(err); + } + Plugins.languageCodes = directories.filter(function(code) { return code !== 'TODO'; }); @@ -206,7 +210,11 @@ var middleware; } }); } else { - winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.'); + if (err) { + winston.error(err); + } else { + winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.'); + } } next(false); @@ -392,7 +400,7 @@ var middleware; next(); }); }, function(err) { - next(null, plugins); + next(err, plugins); }); } ], callback); diff --git a/src/plugins/install.js b/src/plugins/install.js index 1ea826784c..3e3ee42ff3 100644 --- a/src/plugins/install.js +++ b/src/plugins/install.js @@ -96,6 +96,10 @@ module.exports = function(Plugins) { } Plugins.get(id, function(err, pluginData) { + if (err) { + return callback(err); + } + Plugins.fireHook('action:plugin.' + type, id); callback(null, pluginData); }); diff --git a/src/plugins/load.js b/src/plugins/load.js index 70aea70ccd..99019e371f 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -221,6 +221,10 @@ module.exports = function(Plugins) { fallbackMap = {}; utils.walk(pathToFolder, function(err, languages) { + if (err) { + return callback(err); + } + async.each(languages, function(pathToLang, next) { fs.readFile(pathToLang, function(err, file) { if (err) { diff --git a/src/posts/flags.js b/src/posts/flags.js index 9eae966cda..dcc2f32f1d 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -106,6 +106,10 @@ module.exports = function(Posts) { async.series([ function(next) { db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function(err, uids) { + if (err) { + return next(err); + } + async.each(uids, function(uid, next) { var nid = 'post_flag:' + pid + ':uid:' + uid; async.parallel([ diff --git a/src/posts/summary.js b/src/posts/summary.js index 885a0e444d..e3e948c292 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -101,6 +101,10 @@ module.exports = function(Posts) { next(null, post); }); }, function(err, posts) { + if (err) { + return callback(err); + } + plugins.fireHook('filter:post.getPostSummaryByPids', {posts: posts, uid: uid}, function(err, postData) { callback(err, postData.posts); }); diff --git a/src/reset.js b/src/reset.js index f1e27738e8..56669b6e2d 100644 --- a/src/reset.js +++ b/src/reset.js @@ -82,7 +82,12 @@ function resetTheme(themeId) { type: 'local', id: themeId }, function(err) { - winston.info('[reset] Theme reset to ' + themeId); + if (err) { + winston.warn('[reset] Failed to reset theme to ' + themeId); + } else { + winston.info('[reset] Theme reset to ' + themeId); + } + process.exit(); }); } diff --git a/src/rewards/admin.js b/src/rewards/admin.js index e6c32bc34b..0bde13ac00 100644 --- a/src/rewards/admin.js +++ b/src/rewards/admin.js @@ -46,6 +46,10 @@ rewards.save = function(data, callback) { } async.each(data, save, function(err) { + if (err) { + return callback(err); + } + saveConditions(data, callback); }); }; @@ -125,6 +129,10 @@ function getActiveRewards(callback) { } db.getSetMembers('rewards:list', function(err, rewards) { + if (err) { + return callback(err); + } + async.eachSeries(rewards, load, function(err) { callback(err, activeRewards); }); diff --git a/src/rewards/index.js b/src/rewards/index.js index c811ce933c..bbccab69f3 100644 --- a/src/rewards/index.js +++ b/src/rewards/index.js @@ -111,14 +111,22 @@ function getRewardsByRewardData(rewards, callback) { function checkCondition(reward, method, callback) { method(function(err, value) { + if (err) { + return callback(err); + } + plugins.fireHook('filter:rewards.checkConditional:' + reward.conditional, {left: value, right: reward.value}, function(err, bool) { - callback(bool); + callback(err || bool); }); }); } function giveRewards(uid, rewards, callback) { getRewardsByRewardData(rewards, function(err, rewardData) { + if (err) { + return callback(err); + } + async.each(rewards, function(reward, next) { plugins.fireHook('action:rewards.award:' + reward.rid, {uid: uid, reward: rewardData[rewards.indexOf(reward)]}); db.sortedSetIncrBy('uid:' + uid + ':rewards', 1, reward.id, next); diff --git a/src/routes/debug.js b/src/routes/debug.js index b81938ccc9..a24a159e18 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -2,6 +2,7 @@ var express = require('express'), nconf = require('nconf'), + winston = require('winston'), user = require('./../user'), categories = require('./../categories'), topics = require('./../topics'), @@ -16,6 +17,10 @@ module.exports = function(app, middleware, controllers) { } user.getUserData(req.params.uid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { @@ -28,6 +33,10 @@ module.exports = function(app, middleware, controllers) { router.get('/cid/:cid', function (req, res) { categories.getCategoryData(req.params.cid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { @@ -38,6 +47,10 @@ module.exports = function(app, middleware, controllers) { router.get('/tid/:tid', function (req, res) { topics.getTopicData(req.params.tid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { @@ -48,6 +61,10 @@ module.exports = function(app, middleware, controllers) { router.get('/pid/:pid', function (req, res) { posts.getPostData(req.params.pid, function (err, data) { + if (err) { + winston.error(err); + } + if (data) { res.send(data); } else { diff --git a/src/routes/index.js b/src/routes/index.js index 1e51f5d59e..006e5b8c18 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -36,6 +36,9 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/search/:term?', middleware, [], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); + + app.get('/ping', controllers.ping); + app.get('/sping', controllers.ping); } function globalModRoutes(app, middleware, controllers) { @@ -153,6 +156,7 @@ module.exports = function(app, middleware, hotswapIds) { })); app.use('/vendor/jquery/timeago/locales', middleware.processTimeagoLocales); app.use(controllers.handle404); + app.use(controllers.handleURIErrors); app.use(controllers.handleErrors); // Add plugin routes diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 556eee0290..c599abf491 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -251,20 +251,6 @@ SocketAdmin.errors.clear = function(socket, data, callback) { meta.errors.clear(callback); }; -SocketAdmin.getMoreEvents = function(socket, next, callback) { - var start = parseInt(next, 10); - if (start < 0) { - return callback(null, {data: [], next: next}); - } - var stop = start + 10; - events.getEvents(start, stop, function(err, events) { - if (err) { - return callback(err); - } - callback(null, {events: events, next: stop + 1}); - }); -}; - SocketAdmin.deleteAllEvents = function(socket, data, callback) { events.deleteAll(callback); }; diff --git a/src/socket.io/admin/tags.js b/src/socket.io/admin/tags.js index ad7ffa7201..cbe25fabcb 100644 --- a/src/socket.io/admin/tags.js +++ b/src/socket.io/admin/tags.js @@ -1,7 +1,15 @@ "use strict"; -var topics = require('../../topics'), - Tags = {}; +var topics = require('../../topics'); +var Tags = {}; + +Tags.create = function(socket, data, callback) { + if (!data) { + return callback(new Error('[[error:invalid-data]]')); + } + + topics.createEmptyTag(data.tag, callback); +}; Tags.update = function(socket, data, callback) { if (!data) { diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index 706604ec6e..46617c2889 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -172,7 +172,15 @@ SocketHelpers.rescindUpvoteNotification = function(pid, fromuid) { notifications.rescind(nid); posts.getPostField(pid, 'uid', function(err, uid) { + if (err) { + return winston.error(err); + } + user.notifications.getUnreadCount(uid, function(err, count) { + if (err) { + return winston.error(err); + } + websockets.in('uid_' + uid).emit('event:notifications.updateCount', count); }); }); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index cebbfe1a24..3ec75ecd0d 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -56,6 +56,8 @@ var ratelimit = require('../middleware/ratelimit'); } else { socket.join('online_guests'); } + + io.sockets.sockets[socket.id].emit('checkSession', socket.uid); } function onMessage(socket, payload) { diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 3251c3700c..6c86458fc2 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -30,6 +30,7 @@ SocketPosts.reply = function(socket, data, callback) { data.uid = socket.uid; data.req = websockets.reqFromSocket(socket); + data.timestamp = Date.now(); topics.reply(data, function(err, postData) { if (err) { diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 44c17ea476..6bb23a0698 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -122,6 +122,10 @@ SocketUser.reset.commit = function(socket, data, callback) { var parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate(); user.getUserField(uid, 'username', function(err, username) { + if (err) { + return callback(err); + } + emailer.send('reset_notify', uid, { username: username, date: parsedDate, diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index a2c4ea50d3..41c60411e0 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -34,7 +34,20 @@ module.exports = function(SocketUser) { }; SocketUser.unbanUsers = function(socket, uids, callback) { - toggleBan(socket.uid, uids, user.unban, callback); + toggleBan(socket.uid, uids, user.unban, function(err) { + if (err) { + return callback(err); + } + + async.each(uids, function(uid, next) { + events.log({ + type: 'user-unban', + uid: socket.uid, + targetUid: uid, + ip: socket.ip + }, next); + }, callback); + }); }; function toggleBan(uid, uids, method, callback) { diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index cf886b1d48..e71de5f18c 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -38,6 +38,10 @@ module.exports = function(SocketUser) { type: type, picture: undefined }, function(err, returnData) { + if (err) { + return next(err); + } + next(null, returnData.picture || ''); }); break; diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js index 52c9fcd801..48c0f0fa49 100644 --- a/src/socket.io/user/profile.js +++ b/src/socket.io/user/profile.js @@ -29,6 +29,10 @@ module.exports = function(SocketUser) { } user.isAdministrator(socket.uid, function(err, isAdmin) { + if (err) { + return callback(err); + } + if (!isAdmin && data.uid !== socket.uid) { return callback(new Error('[[error:no-privileges]]')); } diff --git a/src/topics.js b/src/topics.js index 0079c501ce..4cb795b2c3 100644 --- a/src/topics.js +++ b/src/topics.js @@ -188,19 +188,27 @@ var social = require('./social'); posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse), category: async.apply(Topics.getCategoryData, topicData.tid), threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}), - tags: async.apply(Topics.getTopicTagsObjects, topicData.tid), isFollowing: async.apply(Topics.isFollowing, [topicData.tid], uid), isIgnoring: async.apply(Topics.isIgnoring, [topicData.tid], uid), bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid), postSharing: async.apply(social.getActivePostSharing), - related: async.apply(Topics.getRelatedTopics, topicData, uid) + related: function(next) { + async.waterfall([ + function(next) { + Topics.getTopicTagsObjects(topicData.tid, next); + }, + function(tags, next) { + topicData.tags = tags; + Topics.getRelatedTopics(topicData, uid, next); + } + ], next); + } }, next); }, function (results, next) { topicData.posts = results.posts; topicData.category = results.category; topicData.thread_tools = results.threadTools.tools; - topicData.tags = results.tags; topicData.isFollowing = results.isFollowing[0]; topicData.isNotFollowing = !results.isFollowing[0] && !results.isIgnoring[0]; topicData.isIgnoring = results.isIgnoring[0]; diff --git a/src/topics/create.js b/src/topics/create.js index cd1bf23a62..ea7d35761a 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -230,7 +230,15 @@ module.exports = function(Topics) { check(content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next); }, function(next) { - posts.create({uid: uid, tid: tid, handle: data.handle, content: content, toPid: data.toPid, timestamp: data.timestamp, ip: data.req ? data.req.ip : null}, next); + posts.create({ + uid: uid, + tid: tid, + handle: data.handle, + content: content, + toPid: data.toPid, + timestamp: data.timestamp, + ip: data.req ? data.req.ip : null + }, next); }, function(_postData, next) { postData = _postData; @@ -328,6 +336,7 @@ module.exports = function(Topics) { } callback(); }); + return; } callback(); } diff --git a/src/topics/delete.js b/src/topics/delete.js index a960ff0421..cf6ae01d8d 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -32,7 +32,7 @@ module.exports = function(Topics) { db.sortedSetRemove('cid:' + topicData.cid + ':pids', pids, next); }); } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -79,7 +79,7 @@ module.exports = function(Topics) { }); }); } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -109,28 +109,35 @@ module.exports = function(Topics) { }; Topics.purge = function(tid, uid, callback) { - async.parallel([ + async.waterfall([ function(next) { - db.deleteAll([ - 'tid:' + tid + ':followers', - 'tid:' + tid + ':ignorers', - 'tid:' + tid + ':posts', - 'tid:' + tid + ':posts:votes', - 'tid:' + tid + ':bookmarks', - 'tid:' + tid + ':posters' + deleteFromFollowersIgnorers(tid, next); + }, + function(next) { + async.parallel([ + function(next) { + db.deleteAll([ + 'tid:' + tid + ':followers', + 'tid:' + tid + ':ignorers', + 'tid:' + tid + ':posts', + 'tid:' + tid + ':posts:votes', + 'tid:' + tid + ':bookmarks', + 'tid:' + tid + ':posters' + ], next); + }, + function(next) { + db.sortedSetsRemove(['topics:tid', 'topics:recent', 'topics:posts', 'topics:views'], tid, next); + }, + function(next) { + deleteTopicFromCategoryAndUser(tid, next); + }, + function(next) { + Topics.deleteTopicTags(tid, next); + }, + function(next) { + reduceCounters(tid, next); + } ], next); - }, - function(next) { - db.sortedSetsRemove(['topics:tid', 'topics:recent', 'topics:posts', 'topics:views'], tid, next); - }, - function(next) { - deleteTopicFromCategoryAndUser(tid, next); - }, - function(next) { - Topics.deleteTopicTags(tid, next); - }, - function(next) { - reduceCounters(tid, next); } ], function(err) { if (err) { @@ -141,6 +148,26 @@ module.exports = function(Topics) { }); }; + function deleteFromFollowersIgnorers(tid, callback) { + async.waterfall([ + function(next) { + async.parallel({ + followers: async.apply(db.getSetMembers, 'tid:' + tid + ':followers'), + ignorers: async.apply(db.getSetMembers, 'tid:' + tid + ':ignorers') + }, next); + }, + function(results, next) { + var followerKeys = results.followers.map(function(uid) { + return 'uid:' + uid + ':followed_tids'; + }); + var ignorerKeys = results.ignorers.map(function(uid) { + return 'uid:' + uid + 'ignored_tids'; + }); + db.sortedSetsRemove(followerKeys.concat(ignorerKeys), tid, next); + } + ], callback); + } + function deleteTopicFromCategoryAndUser(tid, callback) { Topics.getTopicFields(tid, ['cid', 'uid'], function(err, topicData) { if (err) { diff --git a/src/topics/tags.js b/src/topics/tags.js index ec16c09ff6..13dcd4b14d 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -48,6 +48,29 @@ module.exports = function(Topics) { ], callback); }; + Topics.createEmptyTag = function(tag, callback) { + if (!tag) { + return callback(new Error('[[error:invalid-tag]]')); + } + + tag = utils.cleanUpTag(tag, meta.config.maximumTagLength); + if (tag.length < (meta.config.minimumTagLength || 3)) { + return callback(new Error('[[error:tag-too-short]]')); + } + + async.waterfall([ + function(next) { + db.isSortedSetMember('tags:topic:count', tag, next); + }, + function(isMember, next) { + if (isMember) { + return callback(new Error('[[error:tag-exists]]')); + } + db.sortedSetAdd('tags:topic:count', 0, tag, next); + } + ], callback); + }; + Topics.updateTag = function(tag, data, callback) { db.setObject('tag:' + tag, data, callback); }; @@ -233,7 +256,7 @@ module.exports = function(Topics) { updateTagCount(tag, next); }, next); } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -317,7 +340,7 @@ module.exports = function(Topics) { } var maximumTopics = parseInt(meta.config.maximumRelatedTopics, 10) || 0; - if (maximumTopics === 0 || !topicData.tags.length) { + if (maximumTopics === 0 || !topicData.tags || !topicData.tags.length) { return callback(null, []); } diff --git a/src/upgrade.js b/src/upgrade.js index ae3877458d..317f1837da 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -46,6 +46,10 @@ Upgrade.upgrade = function(callback) { function(next) { // Prepare for upgrade & check to make sure the upgrade is possible db.get('schemaDate', function(err, value) { + if (err) { + return next(err); + } + if(!value) { db.set('schemaDate', latestSchema, function() { next(); @@ -506,8 +510,16 @@ Upgrade.upgrade = function(callback) { var privilegesAPI = require('./privileges'); db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + async.eachSeries(cids, function(cid, next) { privilegesAPI.categories.list(cid, function(err, data) { + if (err) { + return next(err); + } + var groups = data.groups; var users = data.users; @@ -628,6 +640,10 @@ Upgrade.upgrade = function(callback) { var meta = require('./meta'); db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + async.eachSeries(cids, function(cid, next) { privilegesAPI.categories.list(cid, function(err, data) { if (err) { @@ -694,8 +710,16 @@ Upgrade.upgrade = function(callback) { var privilegesAPI = require('./privileges'); db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + async.eachSeries(cids, function(cid, next) { privilegesAPI.categories.list(cid, function(err, data) { + if (err) { + return next(err); + } + var groups = data.groups; var users = data.users; diff --git a/src/user/admin.js b/src/user/admin.js index e4384782ee..dd3134e442 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -5,6 +5,7 @@ var async = require('async'); var db = require('../database'); var posts = require('../posts'); var plugins = require('../plugins'); +var winston = require('winston'); module.exports = function(User) { @@ -27,6 +28,7 @@ module.exports = function(User) { }; User.getUsersCSV = function(callback) { + winston.info('[user/getUsersCSV] Compiling User CSV data'); var csvContent = ''; async.waterfall([ diff --git a/src/user/approval.js b/src/user/approval.js index 8ccd5192aa..95b2daa68b 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -166,26 +166,44 @@ module.exports = function(User) { // temporary: see http://www.stopforumspam.com/forum/viewtopic.php?id=6392 user.ip = user.ip.replace('::ffff:', ''); - request({ - method: 'get', - url: 'http://api.stopforumspam.org/api' + - '?ip=' + encodeURIComponent(user.ip) + - '&email=' + encodeURIComponent(user.email) + - '&username=' + encodeURIComponent(user.username) + - '&f=json', - json: true - }, function (err, response, body) { - if (err) { - return next(null, user); - } - if (response.statusCode === 200 && body) { - user.spamData = body; - user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true; - user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true; - user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true; - } + async.parallel([ + function(next) { + User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1, function(err, uids) { + if (err) { + return next(err); + } - next(null, user); + User.getUsersFields(uids, ['uid', 'username', 'picture'], function(err, ipMatch) { + user.ipMatch = ipMatch; + next(err); + }); + }); + }, + function(next) { + request({ + method: 'get', + url: 'http://api.stopforumspam.org/api' + + '?ip=' + encodeURIComponent(user.ip) + + '&email=' + encodeURIComponent(user.email) + + '&username=' + encodeURIComponent(user.username) + + '&f=json', + json: true + }, function (err, response, body) { + if (err) { + return next(); + } + if (response.statusCode === 200 && body) { + user.spamData = body; + user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true; + user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true; + user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true; + } + + next(); + }); + } + ], function(err) { + next(err, user); }); }, next); } diff --git a/src/user/auth.js b/src/user/auth.js index f2fa917962..b8ff96053b 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -101,7 +101,7 @@ module.exports = function(User) { async.each(expiredSids, function(sid, next) { User.auth.revokeSession(sid, uid, next); }, function(err) { - next(null, sessions); + next(err, sessions); }); } ], function (err, sessions) { diff --git a/src/user/digest.js b/src/user/digest.js index 81b6ea0220..f3978bd8e5 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -60,6 +60,10 @@ var utils = require('../../public/src/utils'); Digest.getSubscribers = function(interval, callback) { db.getSortedSetRange('digest:' + interval + ':uids', 0, -1, function(err, subscribers) { + if (err) { + return callback(err); + } + plugins.fireHook('filter:digest.subscribers', { interval: interval, subscribers: subscribers diff --git a/src/user/follow.js b/src/user/follow.js index 094d33c460..7295f01519 100644 --- a/src/user/follow.js +++ b/src/user/follow.js @@ -85,6 +85,10 @@ module.exports = function(User) { start: start, stop: stop }, function(err, data) { + if (err) { + return callback(err); + } + User.getUsers(data.uids, uid, callback); }); }); diff --git a/src/user/picture.js b/src/user/picture.js index 38d013cab0..78ad131ab4 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -200,6 +200,10 @@ module.exports = function(User) { ], function(err) { if (err) { return fs.unlink(data.file.path, function(unlinkErr) { + if (unlinkErr) { + winston.error(unlinkErr); + } + callback(err); // send back the original error }); } diff --git a/src/user/profile.js b/src/user/profile.js index 8b2a83f152..34fb24467c 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -49,6 +49,10 @@ module.exports = function(User) { } User.getUserField(uid, 'email', function(err, email) { + if (err) { + return next(err); + } + if(email === data.email) { return next(); } diff --git a/src/views/400.tpl b/src/views/400.tpl new file mode 100644 index 0000000000..9c263fcff1 --- /dev/null +++ b/src/views/400.tpl @@ -0,0 +1,4 @@ +
    + [[global:400.title]] +

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

    +
    diff --git a/src/views/admin/advanced/events.tpl b/src/views/admin/advanced/events.tpl index f69d7928a5..f1c0973501 100644 --- a/src/views/admin/advanced/events.tpl +++ b/src/views/admin/advanced/events.tpl @@ -23,6 +23,7 @@
    {events.jsonString}
    + diff --git a/src/views/admin/manage/registration.tpl b/src/views/admin/manage/registration.tpl index f751eca74d..5944219794 100644 --- a/src/views/admin/manage/registration.tpl +++ b/src/views/admin/manage/registration.tpl @@ -42,6 +42,15 @@ {users.ip} + +
    + + + +
    {users.ipMatch.icon:text}
    + + {users.ipMatch.username} + diff --git a/src/views/admin/manage/tags.tpl b/src/views/admin/manage/tags.tpl index bc47f93731..d60cdc4147 100644 --- a/src/views/admin/manage/tags.tpl +++ b/src/views/admin/manage/tags.tpl @@ -6,7 +6,7 @@ Your forum does not have any topics with tags yet. - +
    @@ -32,9 +32,10 @@
    -
    Modify Tag
    +
    Create & Modify Tags

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

    +
    @@ -48,4 +49,26 @@
    + +
    diff --git a/tests/categories.js b/tests/categories.js index c5bbee22cc..b607e6fc0a 100644 --- a/tests/categories.js +++ b/tests/categories.js @@ -25,6 +25,8 @@ describe('Categories', function() { blockclass: 'category-blue', order: '5' }, function(err, category) { + assert.equal(err, null); + categoryObj = category; done.apply(this, arguments); }); @@ -41,6 +43,8 @@ describe('Categories', function() { stop: -1, uid: 0 }, function(err, categoryData) { + assert.equal(err, null); + assert(categoryData); assert.equal(categoryObj.name, categoryData.name); assert.equal(categoryObj.description, categoryData.description); @@ -60,6 +64,8 @@ describe('Categories', function() { stop: 10, uid: 0 }, function(err, result) { + assert.equal(err, null); + assert(Array.isArray(result.topics)); assert(result.topics.every(function(topic) { return topic instanceof Object; @@ -79,6 +85,7 @@ describe('Categories', function() { uid: 0, targetUid: 1 }, function(err, result) { + assert.equal(err, null); assert(Array.isArray(result.topics)); assert(result.topics.every(function(topic) { return topic instanceof Object && topic.uid === '1'; diff --git a/tests/database/list.js b/tests/database/list.js index 137c465c2e..c008a0f784 100644 --- a/tests/database/list.js +++ b/tests/database/list.js @@ -125,6 +125,7 @@ describe('List methods', function() { assert.equal(arguments.length, 1); db.getListRange('testList5', 0, -1, function(err, list) { + assert.equal(err, null); assert.equal(Array.isArray(list), true); assert.equal(list.length, 2); assert.equal(list.indexOf('1'), -1); @@ -148,6 +149,7 @@ describe('List methods', function() { assert.equal(err, null); assert.equal(arguments.length, 1); db.getListRange('testList6', 0, -1, function(err, list) { + assert.equal(err, null); assert.equal(list.length, 3); assert.deepEqual(list, ['1', '2', '3']); done(); diff --git a/tests/database/sorted.js b/tests/database/sorted.js index 8f23038dae..c1066f5ff4 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -336,6 +336,18 @@ describe('Sorted Set methods', function() { }); describe('sortedSetScores()', function() { + before(function(done) { + db.sortedSetAdd('zeroScore', 0, 'value1', done); + }); + + it('should return 0 if score is 0', function(done) { + db.sortedSetScores('zeroScore', ['value1'], function(err, scores) { + assert.ifError(err); + assert.equal(0, scores[0]); + done(); + }); + }); + it('should return the scores of value in sorted sets', function(done) { db.sortedSetScores('sortedSetTest1', ['value2', 'value1', 'doesnotexist'], function(err, scores) { assert.equal(err, null); diff --git a/tests/groups.js b/tests/groups.js index 6c30bddd77..43b1857b99 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -270,6 +270,7 @@ describe('Groups', function() { if (err) return done(err); Groups.isMember(1, 'Test', function(err, isMember) { + assert.equal(err, null); assert.strictEqual(true, isMember); done(); @@ -284,6 +285,7 @@ describe('Groups', function() { if (err) return done(err); Groups.isMember(1, 'Test', function(err, isMember) { + assert.equal(err, null); assert.strictEqual(false, isMember); done(); diff --git a/tests/messaging.js b/tests/messaging.js index 3092e42b35..e7a936bae1 100644 --- a/tests/messaging.js +++ b/tests/messaging.js @@ -17,6 +17,10 @@ describe('Messaging Library', function() { async.apply(User.create, { username: 'baz', password: 'quux' }), // restricted user async.apply(User.create, { username: 'herp', password: 'derp' }) // regular user ], function(err, uids) { + if (err) { + return done(err); + } + testUids = uids; async.parallel([ async.apply(Groups.join, 'administrators', uids[0]), diff --git a/tests/mocks/databasemock.js b/tests/mocks/databasemock.js index a895d25c8a..b18d19e338 100644 --- a/tests/mocks/databasemock.js +++ b/tests/mocks/databasemock.js @@ -75,6 +75,10 @@ before(function(done) { db.init(function(err) { + if (err) { + return done(err); + } + //Clean up db.flushdb(function(err) { if(err) { diff --git a/tests/topics.js b/tests/topics.js index 7103b32689..575ce6a0b1 100644 --- a/tests/topics.js +++ b/tests/topics.js @@ -23,6 +23,10 @@ describe('Topic\'s', function() { }; User.create({username: userData.username, password: userData.password, email: userData.email}, function(err, uid) { + if (err) { + return done(err); + } + categories.create({ name: 'Test Category', description: 'Test category created by testing script', @@ -30,6 +34,10 @@ describe('Topic\'s', function() { blockclass: 'category-blue', order: '5' }, function(err, category) { + if (err) { + return done(err); + } + categoryObj = category; topic = { @@ -91,6 +99,10 @@ describe('Topic\'s', function() { before(function(done) { topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { + if (err) { + return done(err); + } + newTopic = result.topicData; newPost = result.postData; done(); @@ -134,6 +146,10 @@ describe('Topic\'s', function() { before(function(done) { topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { + if (err) { + return done(err); + } + newTopic = result.topicData; newPost = result.postData; done(); @@ -188,12 +204,24 @@ describe('Topic\'s', function() { describe('.purge/.delete', function() { var newTopic; - + var followerUid; before(function(done) { - topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { - newTopic = result.topicData; - done(); - }); + async.waterfall([ + function(next) { + topics.post({uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId}, function(err, result) { + assert.ifError(err); + newTopic = result.topicData; + next(); + }); + }, + function(next) { + User.create({username: 'topicFollower', password: '123456'}, next); + }, + function(_uid, next) { + followerUid = _uid; + topics.follow(newTopic.tid, _uid, next); + } + ], done); }); it('should delete the topic', function(done) { @@ -206,7 +234,11 @@ describe('Topic\'s', function() { it('should purge the topic', function(done) { topics.purge(newTopic.tid, 1, function(err) { assert.ifError(err); - done(); + db.isSortedSetMember('uid:' + followerUid + ':followed_tids', newTopic.tid, function(err, isMember) { + assert.ifError(err); + assert.strictEqual(false, isMember); + done(); + }); }); }); }); @@ -220,6 +252,10 @@ describe('Topic\'s', function() { async.waterfall([ function(done){ topics.post({uid: topic.userId, title: 'Topic to be ignored', content: 'Just ignore me, please!', cid: topic.categoryId}, function(err, result) { + if (err) { + return done(err); + } + newTopic = result.topicData; newTid = newTopic.tid; done(); diff --git a/tests/user.js b/tests/user.js index 30a835fe83..32f43c7dbf 100644 --- a/tests/user.js +++ b/tests/user.js @@ -28,6 +28,10 @@ describe('User', function() { description: 'A test', order: 1 }, function(err, categoryObj) { + if (err) { + return done(err); + } + testCid = categoryObj.cid; done(); }); @@ -67,6 +71,7 @@ describe('User', function() { describe('.isModerator()', function() { it('should return false', function(done) { User.isModerator(testUid, testCid, function(err, isModerator) { + assert.equal(err, null); assert.equal(isModerator, false); done(); }); @@ -74,6 +79,7 @@ describe('User', function() { it('should return two false results', function(done) { User.isModerator([testUid, testUid], testCid, function(err, isModerator) { + assert.equal(err, null); assert.equal(isModerator[0], false); assert.equal(isModerator[1], false); done(); @@ -82,6 +88,7 @@ describe('User', function() { it('should return two false results', function(done) { User.isModerator(testUid, [testCid, testCid], function(err, isModerator) { + assert.equal(err, null); assert.equal(isModerator[0], false); assert.equal(isModerator[1], false); done();