Merge remote-tracking branch 'origin/master' into develop

v1.18.x
Julian Lam
commit 9cbf1597cd

@ -37,7 +37,7 @@ var winston = require('winston');
var path = require('path'); var path = require('path');
var pkg = require('./package.json'); var pkg = require('./package.json');
var file = require('./src/file'); var file = require('./src/file');
var debug = require('./src/meta/debugParams')().execArgv.length; var debug = require('./src/meta/debugFork').debugging;
global.env = process.env.NODE_ENV || 'production'; global.env = process.env.NODE_ENV || 'production';

@ -2,7 +2,7 @@
"name": "nodebb", "name": "nodebb",
"license": "GPL-3.0", "license": "GPL-3.0",
"description": "NodeBB Forum", "description": "NodeBB Forum",
"version": "1.6.0", "version": "1.6.1",
"homepage": "http://www.nodebb.org", "homepage": "http://www.nodebb.org",
"repository": { "repository": {
"type": "git", "type": "git",
@ -65,11 +65,11 @@
"nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-soundpack-default": "1.0.0",
"nodebb-plugin-spam-be-gone": "0.5.1", "nodebb-plugin-spam-be-gone": "0.5.1",
"nodebb-rewards-essentials": "0.0.9", "nodebb-rewards-essentials": "0.0.9",
"nodebb-theme-lavender": "4.1.0", "nodebb-theme-lavender": "4.1.1",
"nodebb-theme-persona": "6.1.1", "nodebb-theme-persona": "6.1.3",
"nodebb-theme-slick": "1.1.1", "nodebb-theme-slick": "1.1.1",
"nodebb-theme-vanilla": "7.1.0", "nodebb-theme-vanilla": "7.1.2",
"nodebb-widget-essentials": "3.0.6", "nodebb-widget-essentials": "3.0.7",
"nodemailer": "4.1.1", "nodemailer": "4.1.1",
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-local": "1.0.0", "passport-local": "1.0.0",

@ -15,10 +15,10 @@
"smtp-transport.gmail-warning2": "Další informace o tomto řešení, <a href=\"https://nodemailer.com/usage/using-gmail/\">konzultujte s NodeMailer.</a>Alternativou je použití e-mailového rozšíření třetích stran jako je SendGrid, Mailgun atd.<a href=\"{config.relative_path}/admin/extend/plugins\">Dostupné rozšíření zde</a>.", "smtp-transport.gmail-warning2": "Další informace o tomto řešení, <a href=\"https://nodemailer.com/usage/using-gmail/\">konzultujte s NodeMailer.</a>Alternativou je použití e-mailového rozšíření třetích stran jako je SendGrid, Mailgun atd.<a href=\"{config.relative_path}/admin/extend/plugins\">Dostupné rozšíření zde</a>.",
"smtp-transport.host": "Hostitel SMTP", "smtp-transport.host": "Hostitel SMTP",
"smtp-transport.port": "Port SMTP", "smtp-transport.port": "Port SMTP",
"smtp-transport.security": "Connection security", "smtp-transport.security": "Zabezpečení připojení",
"smtp-transport.security-encrypted": "Encrypted", "smtp-transport.security-encrypted": "Šifrované",
"smtp-transport.security-starttls": "StartTLS", "smtp-transport.security-starttls": "StartTLS",
"smtp-transport.security-none": "None", "smtp-transport.security-none": "Nic",
"smtp-transport.username": "Uživatelské jméno", "smtp-transport.username": "Uživatelské jméno",
"smtp-transport.username-help": "<b>Pro službu Gmail,</b> zadejte plnou e-mailovou adresu, zvláště, používáte-li spravovanou doménu Google Apps.", "smtp-transport.username-help": "<b>Pro službu Gmail,</b> zadejte plnou e-mailovou adresu, zvláště, používáte-li spravovanou doménu Google Apps.",
"smtp-transport.password": "Heslo", "smtp-transport.password": "Heslo",

@ -3,7 +3,9 @@
"enable": "Paginate topics and posts instead of using infinite scroll.", "enable": "Paginate topics and posts instead of using infinite scroll.",
"topics": "Topic Pagination", "topics": "Topic Pagination",
"posts-per-page": "Posts per Page", "posts-per-page": "Posts per Page",
"max-posts-per-page": "Maximum posts per page",
"categories": "Category Pagination", "categories": "Category Pagination",
"topics-per-page": "Topics per Page", "topics-per-page": "Topics per Page",
"max-topics-per-page": "Maximum topics per page",
"initial-num-load": "Initial Number of Topics to Load on Unread, Recent, and Popular" "initial-num-load": "Initial Number of Topics to Load on Unread, Recent, and Popular"
} }

@ -102,6 +102,7 @@
"paginate_description" : "Paginate topics and posts instead of using infinite scroll", "paginate_description" : "Paginate topics and posts instead of using infinite scroll",
"topics_per_page": "Topics per Page", "topics_per_page": "Topics per Page",
"posts_per_page": "Posts per Page", "posts_per_page": "Posts per Page",
"max_items_per_page": "Maximum %1",
"notification_sounds" : "Play a sound when you receive a notification", "notification_sounds" : "Play a sound when you receive a notification",
"notifications_and_sounds": "Notifications & Sounds", "notifications_and_sounds": "Notifications & Sounds",

@ -5,20 +5,20 @@
"from": "Nom de lexpéditeur", "from": "Nom de lexpéditeur",
"from-help": "Le nom de lexpéditeur à afficher dans l'e-mail", "from-help": "Le nom de lexpéditeur à afficher dans l'e-mail",
"smtp-transport": "SMTP Transport", "smtp-transport": "Protocole SMTP",
"smtp-transport.enabled": "Utiliser un server extérieur pour envoyer les emails", "smtp-transport.enabled": "Utiliser un server extérieur pour envoyer les emails",
"smtp-transport-help": "Vous pouvez sélectionner depuis une liste de services ou entrer un service personnalisé.", "smtp-transport-help": "Vous pouvez sélectionner depuis une liste de services ou entrer un service personnalisé.",
"smtp-transport.service": "Sélectionner un service", "smtp-transport.service": "Sélectionner un service",
"smtp-transport.service-custom": "Service personnalisé", "smtp-transport.service-custom": "Service personnalisé",
"smtp-transport.service-help": "S", "smtp-transport.service-help": "Sélectionner un service ci-dessus afin de renseigner les champs. Sinon, sélectionner \"Service Personnalisé\" et ajouter les informations ci-dessous.",
"smtp-transport.gmail-warning1": "There have been reports of the Gmail service not working on accounts with heightened security. In those scenarios, you will have to <a href=\"https://www.google.com/settings/security/lesssecureapps\">configure your GMail account to allow less secure apps</a>.", "smtp-transport.gmail-warning1": "Vous pouvez rencontrer des difficultés avec le service Gmail pour les comptes ayant une sécurité élevée. Dans ce cas, vous devez configurer <a href=\"https://www.google.com/settings/security/lesssecureapps\">votre compte Gmail pour qu'il autorise les applications moins sécurisées</a>.",
"smtp-transport.gmail-warning2": "For more information about this workaround, <a href=\"https://nodemailer.com/usage/using-gmail/\">please consult this NodeMailer article on the issue.</a> An alternative would be to utilise a third-party emailer plugin such as SendGrid, Mailgun, etc. <a href=\"{config.relative_path}/admin/extend/plugins\">Browse available plugins here</a>.", "smtp-transport.gmail-warning2": "Pour plus d'informations à propos de cette solution, <a href=\"https://nodemailer.com/usage/using-gmail/\">consulter l'article sur le NodeMailer sur ce sujet.</a> Une alternative est d'utiliser un service tiers d'envoi d'email tels que SendGrid, Mailgun, etc. <a href=\"{config.relative_path}/admin/extend/plugins\">Consulter les plugins disponibles ici</a>.",
"smtp-transport.host": "Host SMTP", "smtp-transport.host": "Host SMTP",
"smtp-transport.port": "Port SMTP", "smtp-transport.port": "Port SMTP",
"smtp-transport.security": "Connection security", "smtp-transport.security": "Accès sécurisé",
"smtp-transport.security-encrypted": "Encrypted", "smtp-transport.security-encrypted": "Cryptage",
"smtp-transport.security-starttls": "StartTLS", "smtp-transport.security-starttls": "StartTLS",
"smtp-transport.security-none": "None", "smtp-transport.security-none": "Aucun",
"smtp-transport.username": "Nom d'utilisateur", "smtp-transport.username": "Nom d'utilisateur",
"smtp-transport.username-help": "<b>Pour Gmail,</b> entrer ladresse e-mail complète ici, surtout si vous utilisez un domaine géré par Google Apps.", "smtp-transport.username-help": "<b>Pour Gmail,</b> entrer ladresse e-mail complète ici, surtout si vous utilisez un domaine géré par Google Apps.",
"smtp-transport.password": "Mot de passe", "smtp-transport.password": "Mot de passe",

@ -2,8 +2,8 @@
"site-settings": "Réglages du site", "site-settings": "Réglages du site",
"title": "Titre du site", "title": "Titre du site",
"title.url": "URL", "title.url": "URL",
"title.url-placeholder": "The URL of the site title", "title.url-placeholder": "URL du titre du site",
"title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.", "title.url-help": "Adresse à laquelle l'utilisateur est renvoyé lors du clic sur le titre. Si ce champ est vide, l'adresse est celle de l'index du forum.",
"title.name": "Nom de votre communauté", "title.name": "Nom de votre communauté",
"title.show-in-header": "Afficher le titre du site dans l'en-tête", "title.show-in-header": "Afficher le titre du site dans l'en-tête",
"browser-title": "Titre dans le navigateur", "browser-title": "Titre dans le navigateur",

@ -6,8 +6,8 @@
"sorting.most-votes": "Avec le plus de votes", "sorting.most-votes": "Avec le plus de votes",
"sorting.topic-default": "Tri des sujets par défaut", "sorting.topic-default": "Tri des sujets par défaut",
"restrictions": "Restrictions d'envoi", "restrictions": "Restrictions d'envoi",
"restrictions.post-queue": "Enable post queue", "restrictions.post-queue": "Activer la file d'attente des messages",
"restrictions.post-queue-help": "Enabling post queue will put the posts of new users in a queue for approval.", "restrictions.post-queue-help": "Activer la file d'attente des messages mettra automatiquement les messages des nouveaux utilisateurs dans la liste pour approbation.",
"restrictions.seconds-between": "Nombre de secondes entre chaque message", "restrictions.seconds-between": "Nombre de secondes entre chaque message",
"restrictions.seconds-between-new": "Nombre de secondes entre chaque message pour les nouveaux utilisateurs", "restrictions.seconds-between-new": "Nombre de secondes entre chaque message pour les nouveaux utilisateurs",
"restrictions.rep-threshold": "Seuil de réputation avant que cette restriction soit levée", "restrictions.rep-threshold": "Seuil de réputation avant que cette restriction soit levée",

@ -4,7 +4,7 @@
"not-logged-in": "Vous ne semblez pas être connecté.", "not-logged-in": "Vous ne semblez pas être connecté.",
"account-locked": "Votre compte a été temporairement suspendu", "account-locked": "Votre compte a été temporairement suspendu",
"search-requires-login": "Rechercher nécessite d'avoir un compte. Veuillez vous identifier ou vous enregistrer.", "search-requires-login": "Rechercher nécessite d'avoir un compte. Veuillez vous identifier ou vous enregistrer.",
"goback": "Press back to return to the previous page", "goback": "Appuyez sur retour pour revenir à la page précédente",
"invalid-cid": "ID de catégorie invalide", "invalid-cid": "ID de catégorie invalide",
"invalid-tid": "ID de sujet invalide", "invalid-tid": "ID de sujet invalide",
"invalid-pid": "ID de message invalide", "invalid-pid": "ID de message invalide",

@ -41,7 +41,7 @@
"new_register": "<strong>%1</strong> a envoyé une demande d'incription.", "new_register": "<strong>%1</strong> a envoyé une demande d'incription.",
"new_register_multiple": "<strong>%1</strong> inscription(s) est en attente de validation.", "new_register_multiple": "<strong>%1</strong> inscription(s) est en attente de validation.",
"flag_assigned_to_you": "<strong>Drapeau %1</strong> vous a été assigné", "flag_assigned_to_you": "<strong>Drapeau %1</strong> vous a été assigné",
"post_awaiting_review": "Post awaiting review", "post_awaiting_review": "Message en attente de validation",
"email-confirmed": "Email vérifié", "email-confirmed": "Email vérifié",
"email-confirmed-message": "Merci pour la validation de votre adresse email. Votre compte est désormais activé.", "email-confirmed-message": "Merci pour la validation de votre adresse email. Votre compte est désormais activé.",
"email-confirm-error-message": "Il y a un un problème dans la vérification de votre adresse email. Le code est peut être invalide ou a expiré.", "email-confirm-error-message": "Il y a un un problème dans la vérification de votre adresse email. Le code est peut être invalide ou a expiré.",

@ -9,7 +9,7 @@
"moderator-tools": "Outils de modération", "moderator-tools": "Outils de modération",
"flagged-content": "Contenu signalé", "flagged-content": "Contenu signalé",
"ip-blacklist": "Liste noire d'adresses IP", "ip-blacklist": "Liste noire d'adresses IP",
"post-queue": "Post Queue", "post-queue": "File d'attente des messages",
"users/online": "Utilisateurs en ligne", "users/online": "Utilisateurs en ligne",
"users/latest": "Derniers inscrits", "users/latest": "Derniers inscrits",
"users/sort-posts": "Utilisateurs avec le plus de messages", "users/sort-posts": "Utilisateurs avec le plus de messages",

@ -1,7 +1,7 @@
{ {
"success": "Terminé", "success": "Terminé",
"topic-post": "Le message a bien été envoyé.", "topic-post": "Le message a bien été envoyé.",
"post-queued": "Your post is queued for approval.", "post-queued": "Votre message est en attente d'approbation.",
"authentication-successful": "Authentification réussie", "authentication-successful": "Authentification réussie",
"settings-saved": "Paramètres enregistrés !" "settings-saved": "Paramètres enregistrés !"
} }

@ -4,7 +4,7 @@
"not-logged-in": "נראה שאינך מחובר למערכת.", "not-logged-in": "נראה שאינך מחובר למערכת.",
"account-locked": "חשבונך נחסם באופן זמני", "account-locked": "חשבונך נחסם באופן זמני",
"search-requires-login": "פעולת החיפוש דורשת חשבון - בבקשה התחבר או הרשם.", "search-requires-login": "פעולת החיפוש דורשת חשבון - בבקשה התחבר או הרשם.",
"goback": "Press back to return to the previous page", "goback": "לחץ back לחזרה לעמוד הקודם",
"invalid-cid": "זהוי קטגוריה שגוי", "invalid-cid": "זהוי קטגוריה שגוי",
"invalid-tid": "זהוי נושא שגוי", "invalid-tid": "זהוי נושא שגוי",
"invalid-pid": "זהוי פוסט שגוי", "invalid-pid": "זהוי פוסט שגוי",
@ -59,7 +59,7 @@
"post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(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-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": "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-days-hours": "אתה מורשה למחוק פוסט רק %1 ימים ו %2 שעות אחרי פרסומו",
"cant-delete-topic-has-reply": "אינך יכול למחוק נושא אחרי שכבר הגיבו בו.", "cant-delete-topic-has-reply": "אינך יכול למחוק נושא אחרי שכבר הגיבו בו.",
"cant-delete-topic-has-replies": "לא ניתן למחוק את הנושא לאחר שקיבל %1 תגובות", "cant-delete-topic-has-replies": "לא ניתן למחוק את הנושא לאחר שקיבל %1 תגובות",
"content-too-short": "אנא הכנס פוסט ארוך יותר. פוסטים חייבים להכיל לפחות %1 תווים.", "content-too-short": "אנא הכנס פוסט ארוך יותר. פוסטים חייבים להכיל לפחות %1 תווים.",

@ -41,7 +41,7 @@
"new_register": "<strong>%1</strong> שלח בקשת הרשמה.", "new_register": "<strong>%1</strong> שלח בקשת הרשמה.",
"new_register_multiple": "ישנן <strong>%1</strong> בקשות הרשמה שמחכות לבדיקה.", "new_register_multiple": "ישנן <strong>%1</strong> בקשות הרשמה שמחכות לבדיקה.",
"flag_assigned_to_you": "<strong>דיווח %1</strong> הוקצה עבורך", "flag_assigned_to_you": "<strong>דיווח %1</strong> הוקצה עבורך",
"post_awaiting_review": "Post awaiting review", "post_awaiting_review": "הפוסט ממתין לאישור",
"email-confirmed": "כתובת המייל אושרה", "email-confirmed": "כתובת המייל אושרה",
"email-confirmed-message": "תודה שאישרת את כתובת המייל שלך. החשבון שלך פעיל כעת.", "email-confirmed-message": "תודה שאישרת את כתובת המייל שלך. החשבון שלך פעיל כעת.",
"email-confirm-error-message": "אירעה שגיאה בעת אישור המייל שלך. ייתכן כי הקוד היה שגוי או פג תוקף.", "email-confirm-error-message": "אירעה שגיאה בעת אישור המייל שלך. ייתכן כי הקוד היה שגוי או פג תוקף.",

@ -6,10 +6,10 @@
"popular-month": "נושאים חמים החודש", "popular-month": "נושאים חמים החודש",
"popular-alltime": "הנושאים החמים בכל הזמנים", "popular-alltime": "הנושאים החמים בכל הזמנים",
"recent": "נושאים אחרונים", "recent": "נושאים אחרונים",
"moderator-tools": "Moderator Tools", "moderator-tools": "כלי מודרטור",
"flagged-content": "תוכן מדווח", "flagged-content": "תוכן מדווח",
"ip-blacklist": "רשימת IP שחורה", "ip-blacklist": "רשימת IP שחורה",
"post-queue": "Post Queue", "post-queue": "פוסטים ממתינים",
"users/online": "משתמשים מחוברים", "users/online": "משתמשים מחוברים",
"users/latest": "משתמשים אחרונים", "users/latest": "משתמשים אחרונים",
"users/sort-posts": "משתמשים עם המונה הגבוה ביותר", "users/sort-posts": "משתמשים עם המונה הגבוה ביותר",

@ -1,7 +1,7 @@
{ {
"success": "הצלחה", "success": "הצלחה",
"topic-post": "העלת פוסט בהצלחה.", "topic-post": "העלת פוסט בהצלחה.",
"post-queued": "Your post is queued for approval.", "post-queued": "הפוסט שלך ממתין לאישור.",
"authentication-successful": "הנתונים אומתו בהצלחה", "authentication-successful": "הנתונים אומתו בהצלחה",
"settings-saved": "הנתונים נשמרו!" "settings-saved": "הנתונים נשמרו!"
} }

@ -59,7 +59,7 @@
"thread_tools.unlock": "הסר נעילה", "thread_tools.unlock": "הסר נעילה",
"thread_tools.move": "הזז נושא", "thread_tools.move": "הזז נושא",
"thread_tools.move_all": "הזז הכל", "thread_tools.move_all": "הזז הכל",
"thread_tools.select_category": "Select Category", "thread_tools.select_category": "בחר קטגוריה",
"thread_tools.fork": "שכפל נושא", "thread_tools.fork": "שכפל נושא",
"thread_tools.delete": "מחק נושא", "thread_tools.delete": "מחק נושא",
"thread_tools.delete-posts": "מחק פוסטים", "thread_tools.delete-posts": "מחק פוסטים",
@ -75,9 +75,9 @@
"load_categories": "טוען קטגוריות", "load_categories": "טוען קטגוריות",
"confirm_move": "הזז", "confirm_move": "הזז",
"confirm_fork": "שכפל", "confirm_fork": "שכפל",
"bookmark": "Bookmark", "bookmark": "הוסף למועדפים",
"bookmarks": "Bookmarks", "bookmarks": "מועדפים",
"bookmarks.has_no_bookmarks": "You haven't bookmarked any posts yet.", "bookmarks.has_no_bookmarks": "לא צירפת אף פוסט למועדפים עדיין",
"loading_more_posts": "טוען פוסטים נוספים", "loading_more_posts": "טוען פוסטים נוספים",
"move_topic": "הזז נושא", "move_topic": "הזז נושא",
"move_topics": "הזז נושאים", "move_topics": "הזז נושאים",

@ -15,10 +15,10 @@
"smtp-transport.gmail-warning2": "Para mais informação sobre este workaround, <a href=\"https://nodemailer.com/usage/using-gmail/\">por gentileza consulte este artigo no NodeMailer sobre o assunto..</a> Uma alternativa seria utilizar um plugin de email terceirizado como o SendGrid, Maigun etc. . <a href=\"{config.relative_path}/admin/extend/plugins\">Explore pelos plugins disponíveis aqui</a>.", "smtp-transport.gmail-warning2": "Para mais informação sobre este workaround, <a href=\"https://nodemailer.com/usage/using-gmail/\">por gentileza consulte este artigo no NodeMailer sobre o assunto..</a> Uma alternativa seria utilizar um plugin de email terceirizado como o SendGrid, Maigun etc. . <a href=\"{config.relative_path}/admin/extend/plugins\">Explore pelos plugins disponíveis aqui</a>.",
"smtp-transport.host": "Host SMTP", "smtp-transport.host": "Host SMTP",
"smtp-transport.port": "Porta SMTP", "smtp-transport.port": "Porta SMTP",
"smtp-transport.security": "Connection security", "smtp-transport.security": "Segurança da conexão",
"smtp-transport.security-encrypted": "Encrypted", "smtp-transport.security-encrypted": "Encriptada",
"smtp-transport.security-starttls": "StartTLS", "smtp-transport.security-starttls": "StartTLS",
"smtp-transport.security-none": "None", "smtp-transport.security-none": "Nenhuma",
"smtp-transport.username": "Nome de usuário", "smtp-transport.username": "Nome de usuário",
"smtp-transport.username-help": "<b>Para o serviço do Gmail,</b> entre com o endereço de email completo aqui, especiamente se você estiver usando um domínio administrado pelo Google Apps.", "smtp-transport.username-help": "<b>Para o serviço do Gmail,</b> entre com o endereço de email completo aqui, especiamente se você estiver usando um domínio administrado pelo Google Apps.",
"smtp-transport.password": "Senha", "smtp-transport.password": "Senha",

@ -6,8 +6,8 @@
"from-help": "用于邮件中显示的发送者", "from-help": "用于邮件中显示的发送者",
"smtp-transport": "SMTP 通信", "smtp-transport": "SMTP 通信",
"smtp-transport.enabled": "使用一个外部邮箱系统来发送邮件", "smtp-transport.enabled": "使用一个外部电子邮箱系统来发送邮件",
"smtp-transport-help": "可以从列表中选取一个已知的服务或自定义。", "smtp-transport-help": "可以从列表中选取一个已知的服务或自定义。",
"smtp-transport.service": "选择服务", "smtp-transport.service": "选择服务",
"smtp-transport.service-custom": "自定义", "smtp-transport.service-custom": "自定义",
"smtp-transport.service-help": "选取一个上方服务以便使用已知的信息。此外,还可以选取 “自定义”并在下方输入配置细节。", "smtp-transport.service-help": "选取一个上方服务以便使用已知的信息。此外,还可以选取 “自定义”并在下方输入配置细节。",
@ -20,7 +20,7 @@
"smtp-transport.security-starttls": "StartTLS", "smtp-transport.security-starttls": "StartTLS",
"smtp-transport.security-none": "None", "smtp-transport.security-none": "None",
"smtp-transport.username": "用户名", "smtp-transport.username": "用户名",
"smtp-transport.username-help": "<b>对于Gmail服务</b>请在这里输入完整的电子邮件地址,尤其是如果你使用的是 Google Apps 托管的域名。", "smtp-transport.username-help": "<b>对于Gmail服务</b>请在这里输入完整的电子邮箱地址,尤其是如果您使用的是 Google Apps 托管的域名。",
"smtp-transport.password": "密码", "smtp-transport.password": "密码",
"template": "编辑电子邮件模板", "template": "编辑电子邮件模板",

@ -1,7 +1,7 @@
'use strict'; 'use strict';
define('pictureCropper', ['translator', 'cropper', 'benchpress'], function (translator, cropper, Benchpress) { define('pictureCropper', ['translator', 'cropper', 'benchpress'], function (translator, Cropper, Benchpress) {
var module = {}; var module = {};
module.show = function (data, callback) { module.show = function (data, callback) {
@ -46,7 +46,7 @@ define('pictureCropper', ['translator', 'cropper', 'benchpress'], function (tran
var img = document.getElementById('cropped-image'); var img = document.getElementById('cropped-image');
$(img).css('max-height', cropBoxHeight); $(img).css('max-height', cropBoxHeight);
var cropperTool = new cropper.default(img, { var cropperTool = new Cropper(img, {
aspectRatio: data.aspectRatio, aspectRatio: data.aspectRatio,
autoCropArea: 1, autoCropArea: 1,
viewMode: 1, viewMode: 1,
@ -122,7 +122,7 @@ define('pictureCropper', ['translator', 'cropper', 'benchpress'], function (tran
$(this).addClass('disabled'); $(this).addClass('disabled');
cropperTool.destroy(); cropperTool.destroy();
cropperTool = new cropper.default(img, { cropperTool = new Cropper(img, {
viewMode: 1, viewMode: 1,
autoCropArea: 1, autoCropArea: 1,
ready: function () { ready: function () {

@ -12,9 +12,7 @@ var db = require('../../database');
var helpers = require('../helpers'); var helpers = require('../helpers');
var accountHelpers = require('./helpers'); var accountHelpers = require('./helpers');
var settingsController = module.exports;
var settingsController = {};
settingsController.get = function (req, res, callback) { settingsController.get = function (req, res, callback) {
var userData; var userData;
@ -91,15 +89,9 @@ settingsController.get = function (req, res, callback) {
next(err, data); next(err, data);
}); });
}, },
function (data, next) { function (data) {
userData.customSettings = data.customSettings; userData.customSettings = data.customSettings;
userData.disableEmailSubscriptions = parseInt(meta.config.disableEmailSubscriptions, 10) === 1; userData.disableEmailSubscriptions = parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
next();
},
], function (err) {
if (err) {
return callback(err);
}
userData.dailyDigestFreqOptions = [ userData.dailyDigestFreqOptions = [
{ value: 'off', name: '[[user:digest_off]]', selected: userData.settings.dailyDigestFreq === 'off' }, { value: 'off', name: '[[user:digest_off]]', selected: userData.settings.dailyDigestFreq === 'off' },
@ -108,7 +100,6 @@ settingsController.get = function (req, res, callback) {
{ value: 'month', name: '[[user:digest_monthly]]', selected: userData.settings.dailyDigestFreq === 'month' }, { value: 'month', name: '[[user:digest_monthly]]', selected: userData.settings.dailyDigestFreq === 'month' },
]; ];
userData.bootswatchSkinOptions = [ userData.bootswatchSkinOptions = [
{ name: 'No skin', value: 'noskin' }, { name: 'No skin', value: 'noskin' },
{ name: 'Default', value: 'default' }, { name: 'Default', value: 'default' },
@ -147,11 +138,15 @@ settingsController.get = function (req, res, callback) {
userData.inTopicSearchAvailable = plugins.hasListeners('filter:topic.search'); userData.inTopicSearchAvailable = plugins.hasListeners('filter:topic.search');
userData.maxTopicsPerPage = parseInt(meta.config.maxTopicsPerPage, 10) || 20;
userData.maxPostsPerPage = parseInt(meta.config.maxPostsPerPage, 10) || 20;
userData.title = '[[pages:account/settings]]'; userData.title = '[[pages:account/settings]]';
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:settings]]' }]);
res.render('account/settings', userData); res.render('account/settings', userData);
}); },
], callback);
}; };

@ -187,8 +187,11 @@ function addTags(categoryData, res) {
]; ];
if (categoryData.backgroundImage) { if (categoryData.backgroundImage) {
if (!categoryData.backgroundImage.startsWith('http')) {
categoryData.backgroundImage = nconf.get('url') + categoryData.backgroundImage;
}
res.locals.metaTags.push({ res.locals.metaTags.push({
name: 'og:image', property: 'og:image',
content: categoryData.backgroundImage, content: categoryData.backgroundImage,
}); });
} }

@ -390,8 +390,10 @@ Controllers.manifest = function (req, res) {
Controllers.outgoing = function (req, res, next) { Controllers.outgoing = function (req, res, next) {
var url = req.query.url || ''; var url = req.query.url || '';
var allowedProtocols = ['http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn', 'tel', 'fax', 'xmpp', 'webcal'];
var parsed = require('url').parse(url);
if (!url) { if (!url || !allowedProtocols.includes(parsed.protocol.slice(0, -1))) {
return next(); return next();
} }

@ -227,6 +227,8 @@ function addTags(topicData, req, res) {
var ogImageUrl = ''; var ogImageUrl = '';
if (topicData.thumb) { if (topicData.thumb) {
ogImageUrl = topicData.thumb; ogImageUrl = topicData.thumb;
} else if (topicData.category.backgroundImage && (!postAtIndex || !postAtIndex.index)) {
ogImageUrl = topicData.category.backgroundImage;
} else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) { } else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) {
ogImageUrl = postAtIndex.user.picture; ogImageUrl = postAtIndex.user.picture;
} else if (meta.config['og:image']) { } else if (meta.config['og:image']) {

@ -0,0 +1,41 @@
'use strict';
var fork = require('child_process').fork;
var debugArg = process.execArgv.find(function (arg) {
return /^--(debug|inspect)/.test(arg);
});
var debugging = !!debugArg;
debugArg = debugArg ? debugArg.replace('-brk', '').split('=') : ['--debug', 5859];
var lastAddress = parseInt(debugArg[1], 10);
/**
* child-process.fork, but safe for use in debuggers
* @param {string} modulePath
* @param {string[]} [args]
* @param {any} [options]
*/
function debugFork(modulePath, args, options) {
var execArgv = [];
if (global.v8debug || debugging) {
lastAddress += 1;
execArgv = [debugArg[0] + '=' + lastAddress, '--nolazy'];
}
if (!Array.isArray(args)) {
options = args;
args = [];
}
options = options || {};
options = Object.assign({}, options, {
execArgv: execArgv,
});
return fork(modulePath, args, options);
}
debugFork.debugging = debugging;
module.exports = debugFork;

@ -1,16 +0,0 @@
'use strict';
module.exports = function (execArgv) {
execArgv = execArgv || process.execArgv;
var debugArg = execArgv.find(function (arg) {
return /^--(debug|inspect)/.test(arg);
});
if (global.v8debug || debugArg) {
debugArg = debugArg ? debugArg.split('=') : ['--debug', 5859];
var num = parseInt(debugArg[1], 10) + 1;
return { execArgv: [debugArg[0] + '=' + num, '--nolazy'] };
}
return { execArgv: [] };
};

@ -1,7 +1,6 @@
'use strict'; 'use strict';
var fs = require('fs'); var fs = require('fs');
var childProcess = require('child_process');
var os = require('os'); var os = require('os');
var uglifyjs = require('uglify-js'); var uglifyjs = require('uglify-js');
var async = require('async'); var async = require('async');
@ -11,7 +10,7 @@ var postcss = require('postcss');
var autoprefixer = require('autoprefixer'); var autoprefixer = require('autoprefixer');
var clean = require('postcss-clean'); var clean = require('postcss-clean');
var debugParams = require('./debugParams'); var fork = require('./debugFork');
var Minifier = module.exports; var Minifier = module.exports;
@ -47,13 +46,12 @@ function getChild() {
return free.shift(); return free.shift();
} }
var forkProcessParams = debugParams(); var proc = fork(__filename, [], {
var proc = childProcess.fork(__filename, [], Object.assign({}, forkProcessParams, {
cwd: __dirname, cwd: __dirname,
env: { env: {
minifier_child: true, minifier_child: true,
}, },
})); });
pool.push(proc); pool.push(proc);
return proc; return proc;

@ -79,6 +79,7 @@ module.exports = function (middleware) {
uid: 0, uid: 0,
username: '[[global:guest]]', username: '[[global:guest]]',
userslug: '', userslug: '',
fullname: '[[global:guest]]',
email: '', email: '',
picture: user.getDefaultAvatar(), picture: user.getDefaultAvatar(),
status: 'offline', status: 'offline',

@ -1,9 +1,8 @@
'use strict'; 'use strict';
var fork = require('child_process').fork;
var path = require('path'); var path = require('path');
var debugParams = require('./meta/debugParams'); var fork = require('./meta/debugFork');
exports.hash = function (rounds, password, callback) { exports.hash = function (rounds, password, callback) {
forkChild({ type: 'hash', rounds: rounds, password: password }, callback); forkChild({ type: 'hash', rounds: rounds, password: password }, callback);
@ -17,7 +16,7 @@ exports.compare = function (password, hash, callback) {
}; };
function forkChild(message, callback) { function forkChild(message, callback) {
var child = fork(path.join(__dirname, 'bcrypt'), [], debugParams()); var child = fork(path.join(__dirname, 'bcrypt'));
child.on('message', function (msg) { child.on('message', function (msg) {
if (msg.err) { if (msg.err) {

@ -12,12 +12,21 @@ module.exports = function (Posts) {
Posts.getUserInfoForPosts = function (uids, uid, callback) { Posts.getUserInfoForPosts = function (uids, uid, callback) {
var groupsMap = {}; var groupsMap = {};
var userData; var userData;
var userSettings;
async.waterfall([ async.waterfall([
function (next) { function (next) {
async.parallel({
userData: function (next) {
user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status', 'lastonline', 'groupTitle'], next); user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status', 'lastonline', 'groupTitle'], next);
}, },
function (_userData, next) { userSettings: function (next) {
userData = _userData; user.getMultipleUserSettings(uids, next);
},
}, next);
},
function (results, next) {
userData = results.userData;
userSettings = results.userSettings;
var groupTitles = userData.map(function (userData) { var groupTitles = userData.map(function (userData) {
return userData && userData.groupTitle; return userData && userData.groupTitle;
}).filter(function (groupTitle, index, array) { }).filter(function (groupTitle, index, array) {
@ -38,7 +47,7 @@ module.exports = function (Posts) {
} }
}); });
userData.forEach(function (userData) { userData.forEach(function (userData, index) {
userData.uid = userData.uid || 0; userData.uid = userData.uid || 0;
userData.username = userData.username || '[[global:guest]]'; userData.username = userData.username || '[[global:guest]]';
userData.userslug = userData.userslug || ''; userData.userslug = userData.userslug || '';
@ -48,7 +57,7 @@ module.exports = function (Posts) {
userData.picture = userData.picture || ''; userData.picture = userData.picture || '';
userData.status = user.getStatus(userData); userData.status = user.getStatus(userData);
userData.signature = validator.escape(String(userData.signature || '')); userData.signature = validator.escape(String(userData.signature || ''));
userData.fullname = validator.escape(String(userData.fullname || '')); userData.fullname = userSettings[index].showfullname ? validator.escape(String(userData.fullname || '')) : undefined;
if (parseInt(meta.config.hideFullname, 10) === 1) { if (parseInt(meta.config.hideFullname, 10) === 1) {
userData.fullname = undefined; userData.fullname = undefined;
} }

@ -115,6 +115,9 @@ Topics.getTopicsByTids = function (tids, uid, callback) {
users: function (next) { users: function (next) {
user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next); user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next);
}, },
userSettings: function (next) {
user.getMultipleUserSettings(uids, next);
},
categories: function (next) { categories: function (next) {
categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'image', 'bgColor', 'color', 'disabled'], next); categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'image', 'bgColor', 'color', 'disabled'], next);
}, },
@ -136,11 +139,11 @@ Topics.getTopicsByTids = function (tids, uid, callback) {
}, next); }, next);
}, },
function (results, next) { function (results, next) {
if (parseInt(meta.config.hideFullname, 10) === 1) { results.users.forEach(function (user, index) {
results.users.forEach(function (user) { if (parseInt(meta.config.hideFullname, 10) === 1 || !results.userSettings[index].showfullname) {
user.fullname = undefined; user.fullname = undefined;
});
} }
});
var users = _.zipObject(uids, results.users); var users = _.zipObject(uids, results.users);
var categories = _.zipObject(cids, results.categories); var categories = _.zipObject(cids, results.categories);

@ -45,7 +45,7 @@ module.exports = {
} }
meta.configs.setMultiple({ meta.configs.setMultiple({
'brand:logo': path.join(nconf.get('upload_path'), 'system', path.basename(meta.config['brand:logo'])), 'brand:logo': path.join('/assets/uploads/system', path.basename(meta.config['brand:logo'])),
'brand:emailLogo': '/assets/uploads/system/site-logo-x50.png', 'brand:emailLogo': '/assets/uploads/system/site-logo-x50.png',
}, next); }, next);
}, },

@ -12,7 +12,7 @@ var pubsub = require('../pubsub');
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var cache = LRU({ var cache = LRU({
max: 1000, max: 2000,
length: function () { return 1; }, length: function () { return 1; },
maxAge: 1000 * 60 * 60, maxAge: 1000 * 60 * 60,
}); });
@ -135,12 +135,14 @@ module.exports = function (User) {
} }
User.saveSettings = function (uid, data, callback) { User.saveSettings = function (uid, data, callback) {
if (!data.postsPerPage || parseInt(data.postsPerPage, 10) <= 1 || parseInt(data.postsPerPage, 10) > meta.config.postsPerPage) { var maxPostsPerPage = meta.config.maxPostsPerPage || 20;
return callback(new Error('[[error:invalid-pagination-value, 2, ' + meta.config.postsPerPage + ']]')); if (!data.postsPerPage || parseInt(data.postsPerPage, 10) <= 1 || parseInt(data.postsPerPage, 10) > maxPostsPerPage) {
return callback(new Error('[[error:invalid-pagination-value, 2, ' + maxPostsPerPage + ']]'));
} }
if (!data.topicsPerPage || parseInt(data.topicsPerPage, 10) <= 1 || parseInt(data.topicsPerPage, 10) > meta.config.topicsPerPage) { var maxTopicsPerPage = meta.config.maxTopicsPerPage || 20;
return callback(new Error('[[error:invalid-pagination-value, 2, ' + meta.config.topicsPerPage + ']]')); if (!data.topicsPerPage || parseInt(data.topicsPerPage, 10) <= 1 || parseInt(data.topicsPerPage, 10) > maxTopicsPerPage) {
return callback(new Error('[[error:invalid-pagination-value, 2, ' + maxTopicsPerPage + ']]'));
} }
data.userLang = data.userLang || meta.config.defaultLang; data.userLang = data.userLang || meta.config.defaultLang;
@ -153,8 +155,8 @@ module.exports = function (User) {
openOutgoingLinksInNewTab: data.openOutgoingLinksInNewTab, openOutgoingLinksInNewTab: data.openOutgoingLinksInNewTab,
dailyDigestFreq: data.dailyDigestFreq || 'off', dailyDigestFreq: data.dailyDigestFreq || 'off',
usePagination: data.usePagination, usePagination: data.usePagination,
topicsPerPage: Math.min(data.topicsPerPage, parseInt(meta.config.topicsPerPage, 10) || 20), topicsPerPage: Math.min(data.topicsPerPage, parseInt(maxTopicsPerPage, 10) || 20),
postsPerPage: Math.min(data.postsPerPage, parseInt(meta.config.postsPerPage, 10) || 20), postsPerPage: Math.min(data.postsPerPage, parseInt(maxPostsPerPage, 10) || 20),
userLang: data.userLang || meta.config.defaultLang, userLang: data.userLang || meta.config.defaultLang,
followTopicsOnCreate: data.followTopicsOnCreate, followTopicsOnCreate: data.followTopicsOnCreate,
followTopicsOnReply: data.followTopicsOnReply, followTopicsOnReply: data.followTopicsOnReply,

@ -18,7 +18,8 @@
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/pagination:topics]]</div> <div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/pagination:topics]]</div>
<div class="col-sm-10 col-xs-12"> <div class="col-sm-10 col-xs-12">
<form> <form>
<strong>[[admin/settings/pagination:posts-per-page]]</strong><br /> <input type="text" class="form-control" value="20" data-field="postsPerPage"> <strong>[[admin/settings/pagination:posts-per-page]]</strong><br /> <input type="text" class="form-control" value="20" data-field="postsPerPage"><br/>
<strong>[[admin/settings/pagination:max-posts-per-page]]</strong><br /> <input type="text" class="form-control" value="20" data-field="maxPostsPerPage"><br/>
</form> </form>
</div> </div>
</div> </div>
@ -28,6 +29,7 @@
<div class="col-sm-10 col-xs-12"> <div class="col-sm-10 col-xs-12">
<form> <form>
<strong>[[admin/settings/pagination:topics-per-page]]</strong><br /> <input type="text" class="form-control" value="20" data-field="topicsPerPage"><br /> <strong>[[admin/settings/pagination:topics-per-page]]</strong><br /> <input type="text" class="form-control" value="20" data-field="topicsPerPage"><br />
<strong>[[admin/settings/pagination:max-topics-per-page]]</strong><br /> <input type="text" class="form-control" value="20" data-field="maxTopicsPerPage"><br/>
<strong>[[admin/settings/pagination:initial-num-load]]</strong><br /> <input type="text" class="form-control" value="20" data-field="topicsPerList"> <strong>[[admin/settings/pagination:initial-num-load]]</strong><br /> <input type="text" class="form-control" value="20" data-field="topicsPerList">
</form> </form>
</div> </div>

@ -92,18 +92,19 @@ function renderWidget(widget, uid, options, callback) {
} }
if (widget.data.container && widget.data.container.match('{body}')) { if (widget.data.container && widget.data.container.match('{body}')) {
translator.translate(widget.data.title, function (title) {
Benchpress.compileParse(widget.data.container, { Benchpress.compileParse(widget.data.container, {
title: title, title: widget.data.title,
body: html, body: html,
}, function (err, html) { }, next);
next(err, { html: html });
});
});
} else { } else {
next(null, { html: html }); next(null, html);
} }
}, },
function (html, next) {
translator.translate(html, function (translatedHtml) {
next(null, { html: translatedHtml });
});
},
], callback); ], callback);
} }

@ -216,7 +216,7 @@ describe('Controllers', function () {
}); });
it('should load /outgoing?url=<url>', function (done) { it('should load /outgoing?url=<url>', function (done) {
request(nconf.get('url') + '/outgoing?url=http//youtube.com', function (err, res, body) { request(nconf.get('url') + '/outgoing?url=http://youtube.com', function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(body); assert(body);
@ -233,6 +233,15 @@ describe('Controllers', function () {
}); });
}); });
it('should 404 on /outgoing with javascript: protocol', function (done) {
request(nconf.get('url') + '/outgoing?url=javascript:alert(1);', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 404);
assert(body);
done();
});
});
it('should load /tos', function (done) { it('should load /tos', function (done) {
meta.config.termsOfUse = 'please accept our tos'; meta.config.termsOfUse = 'please accept our tos';
request(nconf.get('url') + '/tos', function (err, res, body) { request(nconf.get('url') + '/tos', function (err, res, body) {

@ -276,13 +276,28 @@ describe('meta', function () {
}); });
}); });
describe('debug params', function () { describe('debugFork', function () {
it('should return fork arguments for debug', function (done) { var oldArgv;
var debugParams = require('../src/meta/debugParams'); before(function () {
var data = debugParams(['--debug=5858', '--foo=1']); oldArgv = process.execArgv;
assert.equal(data.execArgv[0], '--debug=5859'); process.execArgv = ['--debug=5858', '--foo=1'];
assert.equal(data.execArgv[1], '--nolazy'); });
it('should detect debugging', function (done) {
var debugFork = require('../src/meta/debugFork');
assert(!debugFork.debugging);
var debugForkPath = require.resolve('../src/meta/debugFork');
delete require.cache[debugForkPath];
debugFork = require('../src/meta/debugFork');
assert(debugFork.debugging);
done(); done();
}); });
after(function () {
process.execArgv = oldArgv;
});
}); });
}); });

Loading…
Cancel
Save