Merge branch 'master' into develop

v1.18.x
Julian Lam 7 years ago
commit 457194b333

@ -38,5 +38,5 @@
"bookmarkThreshold": 5, "bookmarkThreshold": 5,
"topicsPerList": 20, "topicsPerList": 20,
"autoDetectLang": 1, "autoDetectLang": 1,
"privileges:flag": 0 "min:rep:flag": 0
} }

@ -69,9 +69,9 @@
"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": "5.0.0", "nodebb-theme-lavender": "5.0.0",
"nodebb-theme-persona": "7.2.11", "nodebb-theme-persona": "7.2.16",
"nodebb-theme-slick": "1.1.2", "nodebb-theme-slick": "1.1.2",
"nodebb-theme-vanilla": "8.1.5", "nodebb-theme-vanilla": "8.1.7",
"nodebb-widget-essentials": "4.0.1", "nodebb-widget-essentials": "4.0.1",
"nodemailer": "4.4.1", "nodemailer": "4.4.1",
"passport": "^0.4.0", "passport": "^0.4.0",

@ -10,6 +10,7 @@ var less = require('less');
var async = require('async'); var async = require('async');
var uglify = require('uglify-js'); var uglify = require('uglify-js');
var nconf = require('nconf'); var nconf = require('nconf');
var _ = require('lodash');
var Benchpress = require('benchpressjs'); var Benchpress = require('benchpressjs');
var app = express(); var app = express();
@ -103,14 +104,16 @@ function welcome(req, res) {
} }
function install(req, res) { function install(req, res) {
req.setTimeout(0);
var setupEnvVars = _.assign({}, process.env);
for (var i in req.body) { for (var i in req.body) {
if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) { if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) {
process.env[i.replace(':', '__')] = req.body[i]; setupEnvVars[i.replace(':', '__')] = req.body[i];
} }
} }
var child = require('child_process').fork('app', ['--setup'], { var child = require('child_process').fork('app', ['--setup'], {
env: process.env, env: setupEnvVars,
}); });
child.on('close', function (data) { child.on('close', function (data) {

@ -4,5 +4,5 @@
"home-page-route": "Startseitenpfad", "home-page-route": "Startseitenpfad",
"custom-route": "Eigener Startseitenpfad", "custom-route": "Eigener Startseitenpfad",
"allow-user-home-pages": "Benutzern eigene Startseiten erlauben", "allow-user-home-pages": "Benutzern eigene Startseiten erlauben",
"home-page-title": "Title of the home page (default \"Home\")" "home-page-title": "Titel der Startseite (Standardmäßig \"Home\")"
} }

@ -27,8 +27,8 @@
"pills.banned": "Gebannt", "pills.banned": "Gebannt",
"pills.search": "Benutzer Suche", "pills.search": "Benutzer Suche",
"search.uid": "By User ID", "search.uid": "Nach Benutzer-ID",
"search.uid-placeholder": "Enter a user ID to search", "search.uid-placeholder": "Gib eine Benutzer-ID ein um danach zu suchen",
"search.username": "Nach Nutzernamen", "search.username": "Nach Nutzernamen",
"search.username-placeholder": "Einen Nutzernamen eingeben, um danach zu suchen", "search.username-placeholder": "Einen Nutzernamen eingeben, um danach zu suchen",
"search.email": "Nach E-Mail", "search.email": "Nach E-Mail",
@ -71,15 +71,15 @@
"alerts.lockout-reset-success": "Ausschlüsse zurückgesetzt", "alerts.lockout-reset-success": "Ausschlüsse zurückgesetzt",
"alerts.flag-reset-success": "Meldung(en) zurückgesetzt!", "alerts.flag-reset-success": "Meldung(en) zurückgesetzt!",
"alerts.no-remove-yourself-admin": "Du kannst dich nicht selbst als Administrator degradieren!", "alerts.no-remove-yourself-admin": "Du kannst dich nicht selbst als Administrator degradieren!",
"alerts.make-admin-success": "User is now administrator.", "alerts.make-admin-success": "Der Benutzer ist nun ein Administrator",
"alerts.confirm-remove-admin": "Do you really want to remove this administrator?", "alerts.confirm-remove-admin": "Willst du wirklich diesen Administrator entfernen?",
"alerts.remove-admin-success": "User is no longer administrator.", "alerts.remove-admin-success": "Der Benutzer ist kein Administrator mehr",
"alerts.make-global-mod-success": "User is now global moderator.", "alerts.make-global-mod-success": "Der Benutzer ist nun ein globaler Moderator",
"alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?", "alerts.confirm-remove-global-mod": "Willst du wirklich diesen globalen Moderator entfernen?",
"alerts.remove-global-mod-success": "User is no longer global noderator", "alerts.remove-global-mod-success": "Der Benutzer ist kein globaler Moderator mehr",
"alerts.make-moderator-success": "User is now moderator.", "alerts.make-moderator-success": "Der Benutzer ist nun ein Moderator",
"alerts.confirm-remove-moderator": "Do you really want to remove this moderator?", "alerts.confirm-remove-moderator": "Willst du wirklich diesen Moderator entfernen?",
"alerts.remove-moderator-success": "User is no longer moderator.", "alerts.remove-moderator-success": "Der Benutzer ist kein Moderator mehr",
"alerts.confirm-validate-email": "Möchtest Du wirklich die E-Mails dieser Benutzer/dieses Benutzers bestätigen?", "alerts.confirm-validate-email": "Möchtest Du wirklich die E-Mails dieser Benutzer/dieses Benutzers bestätigen?",
"alerts.validate-email-success": "E-Mails bestätigt", "alerts.validate-email-success": "E-Mails bestätigt",
"alerts.password-reset-confirm": "Möchtest Du wirklich (eine) Passwort-Reset-Email(s) an diese(n) Benutzer schicken?", "alerts.password-reset-confirm": "Möchtest Du wirklich (eine) Passwort-Reset-Email(s) an diese(n) Benutzer schicken?",

@ -9,7 +9,7 @@
"section-manage": "Verwalten", "section-manage": "Verwalten",
"manage/categories": "Kategorien", "manage/categories": "Kategorien",
"manage/privileges": "Privileges", "manage/privileges": "Privilegien",
"manage/tags": "Tags", "manage/tags": "Tags",
"manage/users": "Benutzer", "manage/users": "Benutzer",
"manage/admins-mods": "Admins & Mods", "manage/admins-mods": "Admins & Mods",

@ -6,6 +6,6 @@
"max-length": "Maximale Chatnachrichtenlänge", "max-length": "Maximale Chatnachrichtenlänge",
"max-room-size": "Maximale Anzahl an Nutzern pro Chat-Room", "max-room-size": "Maximale Anzahl an Nutzern pro Chat-Room",
"delay": "Zeit zwischen Chatnachrichten in Millisekunden", "delay": "Zeit zwischen Chatnachrichten in Millisekunden",
"restrictions.seconds-edit-after": "Number of seconds before users are allowed to edit chat messages after posting. (0 disabled)", "restrictions.seconds-edit-after": "Zeit in Sekunden bevor Benutzer Chat-Nachrichten editieren dürfen, nachdem sie erstellt wurden. (0 bedeutet deaktiviert)",
"restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete chat messages after posting. (0 disabled)" "restrictions.seconds-delete-after": "Zeit in Sekunden bevor Benutzer Chat-Nachrichten löschen dürfen, nachdem sie erstellt wurden. (0 bedeutet deaktiviert)"
} }

@ -6,25 +6,25 @@
"sorting.most-votes": "Meiste Bewertungen", "sorting.most-votes": "Meiste Bewertungen",
"sorting.most-posts": "Meiste Beiträge", "sorting.most-posts": "Meiste Beiträge",
"sorting.topic-default": "Standardmäßige Themensortierung", "sorting.topic-default": "Standardmäßige Themensortierung",
"length": "Post Length", "length": "Beitragslänge",
"restrictions": "Posting beschränkungen", "restrictions": "Posting beschränkungen",
"restrictions-new": "New User Restrictions", "restrictions-new": "Beschränkungen für neue Benutzer",
"restrictions.post-queue": "Beitragswarteschlange verwenden", "restrictions.post-queue": "Beitragswarteschlange verwenden",
"restrictions-new.post-queue": "Enable new user restrictions", "restrictions-new.post-queue": "Aktiviere Beschränkungen für neue Benutzer",
"restrictions.post-queue-help": "Das verwenden der Beitragswarteschlange wird Beiträge von neuen Benutzern in eine Warteschlange zur Genehmigung setzen.", "restrictions.post-queue-help": "Das verwenden der Beitragswarteschlange wird Beiträge von neuen Benutzern in eine Warteschlange zur Genehmigung setzen.",
"restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users.", "restrictions-new.post-queue-help": "Das aktivieren von Beschränkungen für neue Benutzer wird von neuen Benutzern erstelltw Beiträge beschränken.",
"restrictions.seconds-between": "Seconds between posts", "restrictions.seconds-between": "Sekunden zwischen Beiträgen",
"restrictions.seconds-between-new": "Seconds between posts for new users", "restrictions.seconds-between-new": "Sekunden zwischen Beiträgen für neue Benutzer",
"restrictions.rep-threshold": "Reputation threshold before these restrictions are lifted", "restrictions.rep-threshold": "Mindesreputation bevor die Beschränkungen aufgehoben werden",
"restrictions.seconds-defore-new": "Sekunden befor ein neuer Nutzer einen Beitrag erstellen kann", "restrictions.seconds-defore-new": "Sekunden befor ein neuer Nutzer einen Beitrag erstellen kann",
"restrictions.seconds-edit-after": "Number of seconds before users are allowed to edit posts after posting. (0 disabled)", "restrictions.seconds-edit-after": "Zeit in Sekunden bevor Benutzer ihre Beiträge editieren dürfen, nachdem sie erstellt wurden. (0 bedeutet deaktiviert)",
"restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete posts after posting. (0 disabled)", "restrictions.seconds-delete-after": "Zeit in Sekunden bevor Benutzer ihre Beiträge löschen dürfen, nachdem sie erstellt wurden. (0 bedeutet deaktiviert)",
"restrictions.replies-no-delete": "Anzahl der Antworten auf einen Thema, die Benötigt werden um das löschen des Themas durch den Besitzer zu verhindern. (0 = deaktiviert)", "restrictions.replies-no-delete": "Anzahl der Antworten auf einen Thema, die Benötigt werden um das löschen des Themas durch den Besitzer zu verhindern. (0 = deaktiviert)",
"restrictions.min-title-length": "Minimale Titellänge", "restrictions.min-title-length": "Minimale Titellänge",
"restrictions.max-title-length": "Maximale Titellänge", "restrictions.max-title-length": "Maximale Titellänge",
"restrictions.min-post-length": "Minimale Beitragslänge", "restrictions.min-post-length": "Minimale Beitragslänge",
"restrictions.max-post-length": "Maximale Beitragslänge", "restrictions.max-post-length": "Maximale Beitragslänge",
"restrictions.days-until-stale": "Days until topic is considered stale", "restrictions.days-until-stale": "Tage bis ein Thema als alt angesehen wird",
"restrictions.stale-help": "Wenn ein Thema als \"veraltet\" angesehen wird, wird Nutzern die versuchen diesem Thema zu antworten eine Warnung gezeigt", "restrictions.stale-help": "Wenn ein Thema als \"veraltet\" angesehen wird, wird Nutzern die versuchen diesem Thema zu antworten eine Warnung gezeigt",
"timestamp": "Zeitstempel", "timestamp": "Zeitstempel",
"timestamp.cut-off": "Tageslimit für Relative Zeitangaben (in Tagen)", "timestamp.cut-off": "Tageslimit für Relative Zeitangaben (in Tagen)",

@ -17,7 +17,7 @@
"invalid-login-credentials": "Ungültige Zugangsdaten", "invalid-login-credentials": "Ungültige Zugangsdaten",
"invalid-username-or-password": "Bitte gib sowohl einen Benutzernamen als auch ein Passwort an", "invalid-username-or-password": "Bitte gib sowohl einen Benutzernamen als auch ein Passwort an",
"invalid-search-term": "Ungültige Suchanfrage", "invalid-search-term": "Ungültige Suchanfrage",
"invalid-url": "Invalid URL", "invalid-url": "Ungültige URL",
"csrf-invalid": "Dein Login war nicht erfolgreich da wahrscheinlich deine Sitzung abgelaufen ist. Bitte versuche es noch einmal", "csrf-invalid": "Dein Login war nicht erfolgreich da wahrscheinlich deine Sitzung abgelaufen ist. Bitte versuche es noch einmal",
"invalid-pagination-value": "Ungültige Seitennummerierung, muss mindestens %1 und maximal %2 sein", "invalid-pagination-value": "Ungültige Seitennummerierung, muss mindestens %1 und maximal %2 sein",
"username-taken": "Der Benutzername ist bereits vergeben", "username-taken": "Der Benutzername ist bereits vergeben",
@ -114,8 +114,8 @@
"cant-edit-chat-message": "Du darfst diese Nachricht nicht ändern", "cant-edit-chat-message": "Du darfst diese Nachricht nicht ändern",
"cant-remove-last-user": "Du kannst den letzten Benutzer nicht entfernen", "cant-remove-last-user": "Du kannst den letzten Benutzer nicht entfernen",
"cant-delete-chat-message": "Du darfst diese Nachricht nicht löschen", "cant-delete-chat-message": "Du darfst diese Nachricht nicht löschen",
"chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting", "chat-edit-duration-expired": "Du darfst Chat-Nachrichten nur bis zu %1 Sekunde(n) nach der erstellung verändern",
"chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-delete-duration-expired": "Du darfst Chat-Nachrichten nur bis zu %1 Sekunde(n) nach der erstellung löschen",
"already-voting-for-this-post": "Du hast diesen Beitrag bereits bewertet.", "already-voting-for-this-post": "Du hast diesen Beitrag bereits bewertet.",
"reputation-system-disabled": "Das Reputationssystem ist deaktiviert.", "reputation-system-disabled": "Das Reputationssystem ist deaktiviert.",
"downvoting-disabled": "Downvotes sind deaktiviert.", "downvoting-disabled": "Downvotes sind deaktiviert.",

@ -53,7 +53,7 @@
"topics": "Themen", "topics": "Themen",
"posts": "Beiträge", "posts": "Beiträge",
"best": "Bestbewertet", "best": "Bestbewertet",
"votes": "Votes", "votes": "Stimmen",
"upvoters": "Upvoter", "upvoters": "Upvoter",
"upvoted": "Positiv bewertet", "upvoted": "Positiv bewertet",
"downvoters": "Downvoter", "downvoters": "Downvoter",

@ -6,7 +6,7 @@
"popular-month": "Beliebte Themen dieses Monats", "popular-month": "Beliebte Themen dieses Monats",
"popular-alltime": "Beliebteste Themen", "popular-alltime": "Beliebteste Themen",
"recent": "Neueste Themen", "recent": "Neueste Themen",
"top": "Top Voted Topics", "top": "Bestbewertetste Themen",
"moderator-tools": "Moderator-Werkzeuge", "moderator-tools": "Moderator-Werkzeuge",
"flagged-content": "Gemeldeter Inhalt", "flagged-content": "Gemeldeter Inhalt",
"ip-blacklist": "IP Blacklist", "ip-blacklist": "IP Blacklist",

@ -1,7 +1,7 @@
{ {
"custom-css": "Custom CSS", "custom-css": "Custom CSS/LESS",
"custom-css.description": "Enter your own CSS declarations here, which will be applied after all other styles.", "custom-css.description": "Enter your own CSS/LESS declarations here, which will be applied after all other styles.",
"custom-css.enable": "Enable Custom CSS", "custom-css.enable": "Enable Custom CSS/LESS",
"custom-js": "Custom Javascript", "custom-js": "Custom Javascript",
"custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.", "custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.",

@ -6,6 +6,7 @@
"description": "Select tags via clicking and/or dragging, use shift to select multiple.", "description": "Select tags via clicking and/or dragging, use shift to select multiple.",
"create": "Create Tag", "create": "Create Tag",
"modify": "Modify Tags", "modify": "Modify Tags",
"rename": "Rename Tags",
"delete": "Delete Selected Tags", "delete": "Delete Selected Tags",
"search": "Search for tags...", "search": "Search for tags...",
"settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.", "settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.",

@ -5,5 +5,8 @@
"votes-are-public": "All Votes Are Public", "votes-are-public": "All Votes Are Public",
"thresholds": "Activity Thresholds", "thresholds": "Activity Thresholds",
"min-rep-downvote": "Minimum reputation to downvote posts", "min-rep-downvote": "Minimum reputation to downvote posts",
"min-rep-flag": "Minimum reputation to flag posts" "min-rep-flag": "Minimum reputation to flag posts",
"min-rep-website": "Minimum reputation to add \"Website\" to user profile",
"min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile",
"min-rep-signature": "Minimum reputation to add \"Signature\" to user profile"
} }

@ -148,6 +148,9 @@
"downvoting-disabled": "Downvoting is disabled", "downvoting-disabled": "Downvoting is disabled",
"not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post", "not-enough-reputation-to-downvote": "You do not have enough reputation to downvote this post",
"not-enough-reputation-to-flag": "You do not have enough reputation to flag this post", "not-enough-reputation-to-flag": "You do not have enough reputation to flag this post",
"not-enough-reputation-min-rep-website": "You do not have enough reputation to add a website",
"not-enough-reputation-min-rep-aboutme": "You do not have enough reputation to add an about me",
"not-enough-reputation-min-rep-signature": "You do not have enough reputation to add a signature",
"already-flagged": "You have already flagged this post", "already-flagged": "You have already flagged this post",
"self-vote": "You cannot vote on your own post", "self-vote": "You cannot vote on your own post",

@ -31,8 +31,8 @@
"notices": "Informations", "notices": "Informations",
"restart-not-required": "Pas de redémarrage nécessaire", "restart-not-required": "Pas de redémarrage nécessaire",
"restart-required": "Redémarrage requis", "restart-required": "Redémarrage requis",
"search-plugin-installed": "Recherche un plugin installé", "search-plugin-installed": "Le plugin de recherche est installé",
"search-plugin-not-installed": "Rechercher un plugin non installé", "search-plugin-not-installed": "Le plugin de recherche n'est pas installé",
"search-plugin-tooltip": "Installer un plugin de recherche depuis la page des plugins pour activer la fonctionnalité de recherche", "search-plugin-tooltip": "Installer un plugin de recherche depuis la page des plugins pour activer la fonctionnalité de recherche",
"control-panel": "Contrôle du système", "control-panel": "Contrôle du système",

@ -17,7 +17,7 @@
"invalid-login-credentials": "Неважећи акредитиви за пријављивање", "invalid-login-credentials": "Неважећи акредитиви за пријављивање",
"invalid-username-or-password": "Молимо наведите и корисничко име и лозинку", "invalid-username-or-password": "Молимо наведите и корисничко име и лозинку",
"invalid-search-term": "Неисправан упит за претрагу", "invalid-search-term": "Неисправан упит за претрагу",
"invalid-url": "Invalid URL", "invalid-url": "Неважећа адреса",
"csrf-invalid": "Нисмо успели да вас пријавимо, вероватно због истека сесије. Молимо покушајте поново", "csrf-invalid": "Нисмо успели да вас пријавимо, вероватно због истека сесије. Молимо покушајте поново",
"invalid-pagination-value": "Неважећа вредност приликом нумерисања страница, мора бити најмање %1 а највише %2 ", "invalid-pagination-value": "Неважећа вредност приликом нумерисања страница, мора бити најмање %1 а највише %2 ",
"username-taken": "Корисничко име је заузето", "username-taken": "Корисничко име је заузето",
@ -114,8 +114,8 @@
"cant-edit-chat-message": "Није вам дозвољено да уређујете ову поруку", "cant-edit-chat-message": "Није вам дозвољено да уређујете ову поруку",
"cant-remove-last-user": "Не можете уклонити последњег корисника", "cant-remove-last-user": "Не можете уклонити последњег корисника",
"cant-delete-chat-message": "Није вам дозвољено да избришете ову поруку", "cant-delete-chat-message": "Није вам дозвољено да избришете ову поруку",
"chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting", "chat-edit-duration-expired": "Време у којем вам је дозвољено уређивање порука ћаскања након објављивања: %1 сек.",
"chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", "chat-delete-duration-expired": "Време у којем вам је дозвољено брисање порука ћаскања након објављивања: %1 сек.",
"already-voting-for-this-post": "Већ сте гласали за ову поруку.", "already-voting-for-this-post": "Већ сте гласали за ову поруку.",
"reputation-system-disabled": "Угледи су онемогућени.", "reputation-system-disabled": "Угледи су онемогућени.",
"downvoting-disabled": "Негативно гласање је онемогућено", "downvoting-disabled": "Негативно гласање је онемогућено",

@ -53,7 +53,7 @@
"topics": "Теме", "topics": "Теме",
"posts": "Поруке", "posts": "Поруке",
"best": "Најбоље", "best": "Најбоље",
"votes": "Votes", "votes": "Гласови",
"upvoters": "Позитивно гласали", "upvoters": "Позитивно гласали",
"upvoted": "Позитивно гласано", "upvoted": "Позитивно гласано",
"downvoters": "Негативно гласали", "downvoters": "Негативно гласали",

@ -6,7 +6,7 @@
"popular-month": "Популарне теме овог месеца", "popular-month": "Популарне теме овог месеца",
"popular-alltime": "Популарне теме свих времена", "popular-alltime": "Популарне теме свих времена",
"recent": "Недавне теме", "recent": "Недавне теме",
"top": "Top Voted Topics", "top": "Најгласаније теме",
"moderator-tools": "Алати модератора", "moderator-tools": "Алати модератора",
"flagged-content": "Садржај означен заставицом", "flagged-content": "Садржај означен заставицом",
"ip-blacklist": "Црна листа IP адреса", "ip-blacklist": "Црна листа IP адреса",

@ -52,7 +52,7 @@
"not-watching.description": "Немој ме обавештавати о новим одговорима.<br/>Прикажи тему у непрочитаним ако категорија није игнорисана.", "not-watching.description": "Немој ме обавештавати о новим одговорима.<br/>Прикажи тему у непрочитаним ако категорија није игнорисана.",
"ignoring.description": "Немој ме обавештавати о новим одговорима.<br/>Не приказуј тему у непрочитаним", "ignoring.description": "Немој ме обавештавати о новим одговорима.<br/>Не приказуј тему у непрочитаним",
"thread_tools.title": "Алатке теме", "thread_tools.title": "Алатке теме",
"thread_tools.markAsUnreadForAll": "Mark Unread For All", "thread_tools.markAsUnreadForAll": "Означи као непрочитано за све",
"thread_tools.pin": "Закачи тему", "thread_tools.pin": "Закачи тему",
"thread_tools.unpin": "Откачи тему", "thread_tools.unpin": "Откачи тему",
"thread_tools.lock": "Закључај тему", "thread_tools.lock": "Закључај тему",

@ -30,7 +30,7 @@
"notif.chat.unsub.info": "Bu bildirim şectiğiniz ayarlar yüzünden gönderildi.", "notif.chat.unsub.info": "Bu bildirim şectiğiniz ayarlar yüzünden gönderildi.",
"notif.post.cta": "Konunun tamamını okumak için buraya tıklayın", "notif.post.cta": "Konunun tamamını okumak için buraya tıklayın",
"notif.post.unsub.info": "Bu yazı bildirimi size abonelik ayarlarınız nedeni ile gönderilmiştir.", "notif.post.unsub.info": "Bu yazı bildirimi size abonelik ayarlarınız nedeni ile gönderilmiştir.",
"notif.cta": "Click here to go to forum", "notif.cta": "Foruma gitmek için buraya tıklayın",
"test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.", "test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.",
"unsub.cta": "Buraya tıklayarak ayarlarınızı değiştirebilirsiniz.", "unsub.cta": "Buraya tıklayarak ayarlarınızı değiştirebilirsiniz.",
"banned.subject": "%1 'den yasaklandınız", "banned.subject": "%1 'den yasaklandınız",

@ -53,7 +53,7 @@
"topics": "Başlık", "topics": "Başlık",
"posts": "İleti", "posts": "İleti",
"best": "En İyi", "best": "En İyi",
"votes": "Votes", "votes": "Oy",
"upvoters": "Artı", "upvoters": "Artı",
"upvoted": "Artı", "upvoted": "Artı",
"downvoters": "Eksi", "downvoters": "Eksi",

@ -6,7 +6,7 @@
"popular-month": "Bu ayki popüler başlıklar", "popular-month": "Bu ayki popüler başlıklar",
"popular-alltime": "En popüler başlıklar", "popular-alltime": "En popüler başlıklar",
"recent": "Güncel Konular", "recent": "Güncel Konular",
"top": "Top Voted Topics", "top": "En Çok Oylanan Başlıklar",
"moderator-tools": "Moderatör Araçları", "moderator-tools": "Moderatör Araçları",
"flagged-content": "Bayraklanan İçerik", "flagged-content": "Bayraklanan İçerik",
"ip-blacklist": "IP Kara Listesi", "ip-blacklist": "IP Kara Listesi",
@ -45,7 +45,7 @@
"account/bookmarks": "%1'in yer imine eklenmiş iletiler", "account/bookmarks": "%1'in yer imine eklenmiş iletiler",
"account/settings": "Kullanıcı Ayarları", "account/settings": "Kullanıcı Ayarları",
"account/watched": "%1 tarafından izlenen konular", "account/watched": "%1 tarafından izlenen konular",
"account/ignored": "Topics ignored by %1", "account/ignored": "%1 tarafından konu yok sayıldı",
"account/upvoted": "%1 tarafından artılanan gönderiler", "account/upvoted": "%1 tarafından artılanan gönderiler",
"account/downvoted": "%1 tarafından eksilenen gönderiler", "account/downvoted": "%1 tarafından eksilenen gönderiler",
"account/best": "%1 tarafından en iyi gönderiler", "account/best": "%1 tarafından en iyi gönderiler",

@ -85,7 +85,7 @@
"has_no_posts": "Bu kullanıcı henüz herhangi bir ileti yazmamış.", "has_no_posts": "Bu kullanıcı henüz herhangi bir ileti yazmamış.",
"has_no_topics": "Bu kullanıcı henüz hiç bir başlık açmamış.", "has_no_topics": "Bu kullanıcı henüz hiç bir başlık açmamış.",
"has_no_watched_topics": "Bu kullanıcı henüz hiç bir başlık okumamış.", "has_no_watched_topics": "Bu kullanıcı henüz hiç bir başlık okumamış.",
"has_no_ignored_topics": "This user hasn't ignored any topics yet.", "has_no_ignored_topics": "Bu kullanıcı henüz hiçbir başlığı yok saymadı.",
"has_no_upvoted_posts": "Bu kullanıcı henüz hiç bir gönderiyi artılamamış.", "has_no_upvoted_posts": "Bu kullanıcı henüz hiç bir gönderiyi artılamamış.",
"has_no_downvoted_posts": "Bu kullanıcı henüz hiç bir gönderiyi eksilememiş.", "has_no_downvoted_posts": "Bu kullanıcı henüz hiç bir gönderiyi eksilememiş.",
"has_no_voted_posts": "Bu kullanıcının hiç oylanmış gönderisi yok.", "has_no_voted_posts": "Bu kullanıcının hiç oylanmış gönderisi yok.",
@ -101,11 +101,11 @@
"outgoing-message-sound": "Giden ileti sesi", "outgoing-message-sound": "Giden ileti sesi",
"notification-sound": "Bildirim sesi", "notification-sound": "Bildirim sesi",
"no-sound": "Ses yok", "no-sound": "Ses yok",
"upvote-notif-freq": "Upvote Notification Frequency", "upvote-notif-freq": "Artı Oy Bildiri Sıklığı",
"upvote-notif-freq.all": "Büyün Artı Oylar", "upvote-notif-freq.all": "Büyün Artı Oylar",
"upvote-notif-freq.everyTen": "Every Ten Upvotes", "upvote-notif-freq.everyTen": "Her Artı On Oy",
"upvote-notif-freq.logarithmic": "On 10, 100, 1000...", "upvote-notif-freq.logarithmic": "10, 100, 1000...",
"upvote-notif-freq.disabled": "Disabled", "upvote-notif-freq.disabled": "Devre dışı",
"browsing": "Tarayıcı Ayaları", "browsing": "Tarayıcı Ayaları",
"open_links_in_new_tab": "Dışarı giden bağlantıları yeni sekmede aç", "open_links_in_new_tab": "Dışarı giden bağlantıları yeni sekmede aç",
"enable_topic_searching": "Konu içi aramayı aktive et", "enable_topic_searching": "Konu içi aramayı aktive et",
@ -126,9 +126,9 @@
"sso.title": "Tek giriş servisleri", "sso.title": "Tek giriş servisleri",
"sso.associated": "Birleştirilmiş", "sso.associated": "Birleştirilmiş",
"sso.not-associated": "Birleştirmek için buraya tıklayın", "sso.not-associated": "Birleştirmek için buraya tıklayın",
"sso.dissociate": "Dissociate", "sso.dissociate": "Ayrış",
"sso.dissociate-confirm-title": "Confirm Dissociation", "sso.dissociate-confirm-title": "Ayrışmayı Onayla",
"sso.dissociate-confirm": "Are you sure you wish to dissociate your account from %1?", "sso.dissociate-confirm": "%1 'den ayrışmak istediğinizden emin misiniz?",
"info.latest-flags": "Son Bayraklar", "info.latest-flags": "Son Bayraklar",
"info.no-flags": "Hiç bayraklanan bir ileti bulunamadı", "info.no-flags": "Hiç bayraklanan bir ileti bulunamadı",
"info.ban-history": "Yasaklama Olayları", "info.ban-history": "Yasaklama Olayları",

@ -4,5 +4,5 @@
"home-page-route": "主页路由", "home-page-route": "主页路由",
"custom-route": "自定义路由", "custom-route": "自定义路由",
"allow-user-home-pages": "允许用户主页", "allow-user-home-pages": "允许用户主页",
"home-page-title": "Title of the home page (default \"Home\")" "home-page-title": "首页标题(默认“Home”)"
} }

@ -27,8 +27,8 @@
"pills.banned": "被封禁", "pills.banned": "被封禁",
"pills.search": "搜寻用户", "pills.search": "搜寻用户",
"search.uid": "By User ID", "search.uid": "通过用户名",
"search.uid-placeholder": "Enter a user ID to search", "search.uid-placeholder": "搜索用户ID",
"search.username": "通过用户名", "search.username": "通过用户名",
"search.username-placeholder": "输入你想找的用户名", "search.username-placeholder": "输入你想找的用户名",
"search.email": "通过邮箱", "search.email": "通过邮箱",
@ -71,9 +71,9 @@
"alerts.lockout-reset-success": "闭锁已重置!", "alerts.lockout-reset-success": "闭锁已重置!",
"alerts.flag-reset-success": "举报已重置!", "alerts.flag-reset-success": "举报已重置!",
"alerts.no-remove-yourself-admin": "你无法撤销自己的管理员身份!", "alerts.no-remove-yourself-admin": "你无法撤销自己的管理员身份!",
"alerts.make-admin-success": "User is now administrator.", "alerts.make-admin-success": "用户已成为管理员",
"alerts.confirm-remove-admin": "Do you really want to remove this administrator?", "alerts.confirm-remove-admin": "真的想要删除这个管理员?",
"alerts.remove-admin-success": "User is no longer administrator.", "alerts.remove-admin-success": "此用户不再是管理员",
"alerts.make-global-mod-success": "User is now global moderator.", "alerts.make-global-mod-success": "User is now global moderator.",
"alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?", "alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?",
"alerts.remove-global-mod-success": "User is no longer global noderator", "alerts.remove-global-mod-success": "User is no longer global noderator",

@ -14,7 +14,7 @@ define('admin/appearance/customise', ['admin/settings', 'ace/ace'], function (Se
var customHTML = ace.edit('customHTML'); var customHTML = ace.edit('customHTML');
customCSS.setTheme('ace/theme/twilight'); customCSS.setTheme('ace/theme/twilight');
customCSS.getSession().setMode('ace/mode/css'); customCSS.getSession().setMode('ace/mode/less');
customCSS.on('change', function () { customCSS.on('change', function () {
app.flags = app.flags || {}; app.flags = app.flags || {};

@ -15,6 +15,7 @@ define('admin/manage/tags', [
handleCreate(); handleCreate();
handleSearch(); handleSearch();
handleModify(); handleModify();
handleRename();
handleDeleteSelected(); handleDeleteSelected();
}; };
@ -103,15 +104,25 @@ define('admin/manage/tags', [
var modal = $('.bootbox'); var modal = $('.bootbox');
var bgColor = modal.find('[data-name="bgColor"]').val(); var bgColor = modal.find('[data-name="bgColor"]').val();
var color = modal.find('[data-name="color"]').val(); var color = modal.find('[data-name="color"]').val();
var data = [];
tagsToModify.each(function (idx, tag) { tagsToModify.each(function (idx, tag) {
tag = $(tag); tag = $(tag);
data.push({
value: tag.attr('data-tag'),
color: modal.find('[data-name="color"]').val(),
bgColor: modal.find('[data-name="bgColor"]').val(),
});
tag.find('[data-name="bgColor"]').val(bgColor); tag.find('[data-name="bgColor"]').val(bgColor);
tag.find('[data-name="color"]').val(color); tag.find('[data-name="color"]').val(color);
tag.find('.tag-item').css('background-color', bgColor).css('color', color); tag.find('.tag-item').css('background-color', bgColor).css('color', color);
});
save(tag); socket.emit('admin.tags.update', data, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/tags:alerts.update-success]]');
}); });
}, },
}, },
@ -122,6 +133,46 @@ define('admin/manage/tags', [
}); });
} }
function handleRename() {
$('#rename').on('click', function () {
var tagsToModify = $('.tag-row.ui-selected');
if (!tagsToModify.length) {
return;
}
var firstTag = $(tagsToModify[0]);
var title = tagsToModify.length > 1 ? '[[admin/manage/tags:alerts.editing-multiple]]' : '[[admin/manage/tags:alerts.editing-x, ' + firstTag.find('.tag-item').attr('data-tag') + ']]';
var modal = bootbox.dialog({
title: title,
message: $('.rename-modal').html(),
buttons: {
success: {
label: 'Save',
className: 'btn-primary save',
callback: function () {
var data = [];
tagsToModify.each(function (idx, tag) {
tag = $(tag);
data.push({
value: tag.attr('data-tag'),
newName: modal.find('[data-name="value"]').val(),
});
});
socket.emit('admin.tags.rename', data, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/tags:alerts.update-success]]');
});
},
},
},
});
});
}
function handleDeleteSelected() { function handleDeleteSelected() {
$('#deleteSelected').on('click', function () { $('#deleteSelected').on('click', function () {
var tagsToDelete = $('.tag-row.ui-selected'); var tagsToDelete = $('.tag-row.ui-selected');
@ -158,21 +209,5 @@ define('admin/manage/tags', [
modal.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker); modal.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker);
} }
function save(tag) {
var data = {
tag: tag.attr('data-tag'),
bgColor: tag.find('[data-name="bgColor"]').val(),
color: tag.find('[data-name="color"]').val(),
};
socket.emit('admin.tags.update', data, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/tags:alerts.update-success]]');
});
}
return Tags; return Tags;
}); });

@ -31,7 +31,7 @@ define('forum/account/edit/email', ['forum/account/header'], function (header) {
return app.alertError(err.message); return app.alertError(err.message);
} }
ajaxify.go('user/' + ajaxify.data.userslug); ajaxify.go('user/' + ajaxify.data.userslug + '/edit');
}); });
return false; return false;

@ -82,8 +82,11 @@ define('forum/account/edit/password', ['forum/account/header', 'translator', 'zx
onPasswordConfirmChanged(); onPasswordConfirmChanged();
return app.alertError(err.message); return app.alertError(err.message);
} }
if (parseInt(app.user.uid, 10) === parseInt(ajaxify.data.uid, 10)) {
window.location.href = config.relative_path + '/login'; window.location.href = config.relative_path + '/login';
} else {
ajaxify.go('user/' + ajaxify.data.userslug + '/edit');
}
}); });
} else { } else {
if (!passwordsmatch) { if (!passwordsmatch) {

@ -39,7 +39,7 @@ define('forum/account/edit/username', ['forum/account/header'], function (header
$('[component="header/usericon"]').css('background-color', data['icon:bgColor']).text(data['icon:text']); $('[component="header/usericon"]').css('background-color', data['icon:bgColor']).text(data['icon:text']);
} }
ajaxify.go('user/' + userslug); ajaxify.go('user/' + userslug + '/edit');
}); });
return false; return false;

@ -545,6 +545,20 @@
return str.toString().replace(escapeChars, replaceChar); return str.toString().replace(escapeChars, replaceChar);
}, },
addNoReferrer: function (containerEl) {
containerEl.find('a').attr('rel', function (idx, value) {
value = value ? value.split(' ') : [];
['noopener', 'noreferrer'].forEach(function (property) {
if (!value.includes(property)) {
value.push(property);
}
});
return value.join(' ');
});
},
isAndroidBrowser: function () { isAndroidBrowser: function () {
// http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser // http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser
var nua = navigator.userAgent; var nua = navigator.userAgent;
@ -732,6 +746,27 @@
rtrim: function (str) { rtrim: function (str) {
return str.replace(/\s+$/g, ''); return str.replace(/\s+$/g, '');
}, },
debounce: function (func, wait, immediate) {
// modified from https://davidwalsh.name/javascript-debounce-function
var timeout;
return function () {
var context = this;
var args = arguments;
var later = function () {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
},
}; };
/* eslint "no-extend-native": "off" */ /* eslint "no-extend-native": "off" */

@ -58,6 +58,7 @@
title: $(this).attr('title'), title: $(this).attr('title'),
}); });
}); });
utils.addNoReferrer(widgetAreas);
$(window).trigger('action:widgets.loaded', {}); $(window).trigger('action:widgets.loaded', {});
callback(); callback();
}; };

@ -339,10 +339,6 @@ Categories.buildForSelect = function (uid, privilege, callback) {
Categories.buildForSelectCategories = function (categories, callback) { Categories.buildForSelectCategories = function (categories, callback) {
function recursive(category, categoriesData, level, depth) { function recursive(category, categoriesData, level, depth) {
if (category.link) {
return;
}
var bullet = level ? '&bull; ' : ''; var bullet = level ? '&bull; ' : '';
category.value = category.cid; category.value = category.cid;
category.level = level; category.level = level;
@ -358,7 +354,7 @@ Categories.buildForSelectCategories = function (categories, callback) {
var categoriesData = []; var categoriesData = [];
categories = categories.filter(function (category) { categories = categories.filter(function (category) {
return category && !category.link && !parseInt(category.parentCid, 10); return category && !parseInt(category.parentCid, 10);
}); });
categories.forEach(function (category) { categories.forEach(function (category) {

@ -8,9 +8,9 @@ var dirname = require('./paths').baseDir;
// check to make sure dependencies are installed // check to make sure dependencies are installed
try { try {
require('../../package.json'); fs.accessSync(path.join(dirname, 'package.json'), fs.constants.R_OK);
} catch (e) { } catch (e) {
if (e.code === 'MODULE_NOT_FOUND') { if (e.code === 'ENOENT') {
console.warn('package.json not found.'); console.warn('package.json not found.');
console.log('Populating package.json...'); console.log('Populating package.json...');
@ -18,6 +18,8 @@ try {
packageInstall.preserveExtraneousPlugins(); packageInstall.preserveExtraneousPlugins();
try { try {
fs.accessSync(path.join(dirname, 'node_modules/colors/package.json'), fs.constants.R_OK);
require('colors'); require('colors');
console.log('OK'.green); console.log('OK'.green);
} catch (e) { } catch (e) {
@ -29,6 +31,8 @@ try {
} }
try { try {
fs.accessSync(path.join(dirname, 'node_modules/semver/package.json'), fs.constants.R_OK);
var semver = require('semver'); var semver = require('semver');
var defaultPackage = require('../../install/package.json'); var defaultPackage = require('../../install/package.json');
@ -50,6 +54,7 @@ try {
console.warn('Dependencies outdated or not yet installed.'); console.warn('Dependencies outdated or not yet installed.');
console.log('Installing them now...\n'); console.log('Installing them now...\n');
packageInstall.updatePackageFile();
packageInstall.installAll(); packageInstall.installAll();
require('colors'); require('colors');
@ -292,16 +297,12 @@ program
} }
}); });
program
.command('*', {}, {
noHelp: true,
})
.action(function () {
program.help();
});
require('./colors'); require('./colors');
if (process.argv.length === 2) {
program.help();
}
program.executables = false; program.executables = false;
program.parse(process.argv); program.parse(process.argv);

@ -33,6 +33,7 @@ function installAll() {
var prod = global.env !== 'development'; var prod = global.env !== 'development';
var command = 'npm install'; var command = 'npm install';
try { try {
fs.accessSync(path.join(modulesPath, 'nconf/package.json'), fs.constants.R_OK);
var packageManager = require('nconf').get('package_manager'); var packageManager = require('nconf').get('package_manager');
if (packageManager === 'yarn') { if (packageManager === 'yarn') {
command = 'yarn'; command = 'yarn';

@ -28,6 +28,9 @@ editController.get = function (req, res, callback) {
userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10); userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10);
userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads, 10) === 1; userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads, 10) === 1;
userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1; userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1;
userData.allowWebsite = !userData.isSelf || parseInt(userData.reputation, 10) >= (parseInt(meta.config['min:rep:website'], 10) || 0);
userData.allowAboutMe = !userData.isSelf || parseInt(userData.reputation, 10) >= (parseInt(meta.config['min:rep:aboutme'], 10) || 0);
userData.allowSignature = !userData.isSelf || parseInt(userData.reputation, 10) >= (parseInt(meta.config['min:rep:signature'], 10) || 0);
userData.profileImageDimension = parseInt(meta.config.profileImageDimension, 10) || 200; userData.profileImageDimension = parseInt(meta.config.profileImageDimension, 10) || 200;
userData.defaultAvatar = user.getDefaultAvatar(); userData.defaultAvatar = user.getDefaultAvatar();

@ -126,7 +126,7 @@ modsController.flags.detail = function (req, res, next) {
assignees: results.assignees, assignees: results.assignees,
type_bool: ['post', 'user', 'empty'].reduce(function (memo, cur) { type_bool: ['post', 'user', 'empty'].reduce(function (memo, cur) {
if (cur !== 'empty') { if (cur !== 'empty') {
memo[cur] = results.flagData.type === cur && !!Object.keys(results.flagData.target).length; memo[cur] = results.flagData.type === cur && (!results.flagData.target || !!Object.keys(results.flagData.target).length);
} else { } else {
memo[cur] = !Object.keys(results.flagData.target).length; memo[cur] = !Object.keys(results.flagData.target).length;
} }

@ -54,6 +54,10 @@ searchController.search = function (req, res, next) {
return next(err); return next(err);
} }
results.categories = results.categories.filter(function (category) {
return category && !category.link;
});
var categoriesData = [ var categoriesData = [
{ value: 'all', text: '[[unread:all_categories]]' }, { value: 'all', text: '[[unread:all_categories]]' },
{ value: 'watched', text: '[[category:watched-categories]]' }, { value: 'watched', text: '[[category:watched-categories]]' },

@ -37,8 +37,12 @@ redisModule.questions = [
]; ];
redisModule.init = function (callback) { redisModule.init = function (callback) {
redisClient = redisModule.connect(); callback = callback || function () { };
redisClient = redisModule.connect({}, function (err) {
if (err) {
winston.error('NodeBB could not connect to your Redis database. Redis returned the following error', err);
return callback(err);
}
redisModule.client = redisClient; redisModule.client = redisClient;
require('./redis/main')(redisClient, redisModule); require('./redis/main')(redisClient, redisModule);
@ -47,9 +51,8 @@ redisModule.init = function (callback) {
require('./redis/sorted')(redisClient, redisModule); require('./redis/sorted')(redisClient, redisModule);
require('./redis/list')(redisClient, redisModule); require('./redis/list')(redisClient, redisModule);
if (typeof callback === 'function') {
callback(); callback();
} });
}; };
redisModule.initSessionStore = function (callback) { redisModule.initSessionStore = function (callback) {
@ -66,7 +69,8 @@ redisModule.initSessionStore = function (callback) {
} }
}; };
redisModule.connect = function (options) { redisModule.connect = function (options, callback) {
callback = callback || function () {};
var redis_socket_or_host = nconf.get('redis:host'); var redis_socket_or_host = nconf.get('redis:host');
var cxn; var cxn;
@ -88,7 +92,11 @@ redisModule.connect = function (options) {
cxn.on('error', function (err) { cxn.on('error', function (err) {
winston.error(err.stack); winston.error(err.stack);
process.exit(1); callback(err);
});
cxn.on('ready', function () {
callback();
}); });
if (nconf.get('redis:password')) { if (nconf.get('redis:password')) {
@ -99,7 +107,7 @@ redisModule.connect = function (options) {
if (dbIdx >= 0) { if (dbIdx >= 0) {
cxn.select(dbIdx, function (err) { cxn.select(dbIdx, function (err) {
if (err) { if (err) {
winston.error('NodeBB could not connect to your Redis database. Redis returned the following error', err); winston.error('NodeBB could not select Redis database. Redis returned the following error', err);
throw err; throw err;
} }
}); });

@ -10,6 +10,7 @@ var htmlToText = require('html-to-text');
var url = require('url'); var url = require('url');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var _ = require('lodash');
var User = require('./user'); var User = require('./user');
var Plugins = require('./plugins'); var Plugins = require('./plugins');
@ -289,11 +290,10 @@ function buildCustomTemplates(config) {
file.walk(viewsDir, next); file.walk(viewsDir, next);
}, },
function (paths, next) { function (paths, next) {
paths = paths.reduce(function (obj, p) { paths = _.fromPairs(paths.map(function (p) {
var relative = path.relative(viewsDir, p); var relative = path.relative(viewsDir, p).replace(/\\/g, '/');
obj['/' + relative] = p; return [relative, p];
return obj; }));
}, {});
meta.templates.processImports(paths, template.path, template.text, next); meta.templates.processImports(paths, template.path, template.text, next);
}, },
function (source, next) { function (source, next) {

@ -241,7 +241,7 @@ Flags.validate = function (payload, callback) {
return callback(err); return callback(err);
} }
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0; var minimumReputation = utils.isNumber(meta.config['min:rep:flag']) ? parseInt(meta.config['min:rep:flag'], 10) : 0;
// Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply) // Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply)
if (!editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) { if (!editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) {
return callback(new Error('[[error:not-enough-reputation-to-flag]]')); return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
@ -257,7 +257,7 @@ Flags.validate = function (payload, callback) {
return callback(err); return callback(err);
} }
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0; var minimumReputation = utils.isNumber(meta.config['min:rep:flag']) ? parseInt(meta.config['min:rep:flag'], 10) : 0;
// Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply) // Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply)
if (!editable && parseInt(data.reporter.reputation, 10) < minimumReputation) { if (!editable && parseInt(data.reporter.reputation, 10) < minimumReputation) {
return callback(new Error('[[error:not-enough-reputation-to-flag]]')); return callback(new Error('[[error:not-enough-reputation-to-flag]]'));

@ -157,16 +157,17 @@ function completeConfigSetup(config, next) {
} }
} }
nconf.overrides(config);
async.waterfall([ async.waterfall([
function (next) {
install.save(config, next);
},
function (next) { function (next) {
require('./database').init(next); require('./database').init(next);
}, },
function (next) { function (next) {
require('./database').createIndices(next); require('./database').createIndices(next);
}, },
function (next) {
install.save(config, next);
},
], next); ], next);
} }
@ -523,7 +524,7 @@ install.setup = function (callback) {
], function (err, results) { ], function (err, results) {
if (err) { if (err) {
winston.warn('NodeBB Setup Aborted.\n ' + err.stack); winston.warn('NodeBB Setup Aborted.\n ' + err.stack);
process.exit(); process.exit(1);
} else { } else {
var data = {}; var data = {};
if (results[6]) { if (results[6]) {

@ -343,6 +343,11 @@ JS.buildBundle = function (target, fork, callback) {
function (next) { function (next) {
getBundleScriptList(target, next); getBundleScriptList(target, next);
}, },
function (files, next) {
mkdirp(path.join(__dirname, '../../build/public'), function (err) {
next(err, files);
});
},
function (files, next) { function (files, next) {
var minify = global.env !== 'development'; var minify = global.env !== 'development';
var filePath = path.join(__dirname, '../../build/public', fileNames[target]); var filePath = path.join(__dirname, '../../build/public', fileNames[target]);

@ -75,7 +75,7 @@ function forkAction(action, callback) {
freeChild(proc); freeChild(proc);
if (message.type === 'error') { if (message.type === 'error') {
return callback(message.err); return callback(message.message);
} }
if (message.type === 'end') { if (message.type === 'end') {
@ -103,7 +103,7 @@ if (process.env.minifier_child) {
if (typeof actions[action.act] !== 'function') { if (typeof actions[action.act] !== 'function') {
process.send({ process.send({
type: 'error', type: 'error',
err: Error('Unknown action'), message: 'Unknown action',
}); });
return; return;
} }
@ -112,7 +112,7 @@ if (process.env.minifier_child) {
if (err) { if (err) {
process.send({ process.send({
type: 'error', type: 'error',
err: err, message: err.stack,
}); });
return; return;
} }

@ -7,6 +7,7 @@ var async = require('async');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var nconf = require('nconf'); var nconf = require('nconf');
var _ = require('lodash');
var plugins = require('../plugins'); var plugins = require('../plugins');
var file = require('../file'); var file = require('../file');
@ -24,7 +25,7 @@ function processImports(paths, templatePath, source, callback) {
return callback(null, source); return callback(null, source);
} }
var partial = '/' + matches[1]; var partial = matches[1];
if (paths[partial] && templatePath !== partial) { if (paths[partial] && templatePath !== partial) {
fs.readFile(paths[partial], 'utf8', function (err, partialSource) { fs.readFile(paths[partial], 'utf8', function (err, partialSource) {
if (err) { if (err) {
@ -43,124 +44,108 @@ function processImports(paths, templatePath, source, callback) {
} }
Templates.processImports = processImports; Templates.processImports = processImports;
Templates.compile = function (callback) { function getTemplateDirs(callback) {
callback = callback || function () {}; var pluginTemplates = _.values(plugins.pluginsData)
.filter(function (pluginData) {
return !pluginData.id.startsWith('nodebb-theme-');
})
.map(function (pluginData) {
return path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.templates || 'templates');
});
var themeConfig = require(nconf.get('theme_config')); var themeConfig = require(nconf.get('theme_config'));
var baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')]; var theme = themeConfig.baseTheme;
var themePath;
var themeTemplates = [nconf.get('theme_templates_path')];
while (theme) {
themePath = path.join(nconf.get('themes_path'), theme);
themeConfig = require(path.join(themePath, 'theme.json'));
themeTemplates.push(path.join(themePath, themeConfig.templates || 'templates'));
theme = themeConfig.baseTheme;
}
themeTemplates.push(nconf.get('base_templates_path'));
themeTemplates = _.uniq(themeTemplates.reverse());
var coreTemplatesPath = nconf.get('core_templates_path');
var templateDirs = _.uniq([coreTemplatesPath].concat(themeTemplates, pluginTemplates));
async.filter(templateDirs, file.exists, callback);
}
function getTemplateFiles(dirs, callback) {
async.waterfall([ async.waterfall([
function (next) { function (cb) {
preparePaths(baseTemplatesPaths, next); async.map(dirs, function (dir, next) {
}, file.walk(dir, function (err, files) {
function (paths, next) { if (err) { return next(err); }
async.each(Object.keys(paths), function (relativePath, next) {
async.waterfall([ files = files.filter(function (path) {
function (next) { return path.endsWith('.tpl');
fs.readFile(paths[relativePath], 'utf8', next); }).map(function (file) {
}, return {
function (source, next) { name: path.relative(dir, file).replace(/\\/g, '/'),
processImports(paths, relativePath, source, next); path: file,
}, };
function (source, next) {
mkdirp(path.join(viewsPath, path.dirname(relativePath)), function (err) {
next(err, source);
}); });
next(null, files);
});
}, cb);
}, },
function (compiled, next) { function (buckets, cb) {
fs.writeFile(path.join(viewsPath, relativePath), compiled, next); var dict = {};
}, buckets.forEach(function (files) {
], next); files.forEach(function (file) {
}, next); dict[file.name] = file.path;
}, });
function (next) { });
rimraf(path.join(viewsPath, '*.js'), next);
}, cb(null, dict);
function (next) {
winston.verbose('[meta/templates] Successfully compiled templates.');
next();
}, },
], callback); ], callback);
};
function getBaseTemplates(theme) {
var baseTemplatesPaths = [];
var baseThemePath;
var baseThemeConfig;
while (theme) {
baseThemePath = path.join(nconf.get('themes_path'), theme);
baseThemeConfig = require(path.join(baseThemePath, 'theme.json'));
baseTemplatesPaths.push(path.join(baseThemePath, baseThemeConfig.templates || 'templates'));
theme = baseThemeConfig.baseTheme;
} }
return baseTemplatesPaths.reverse(); function compile(callback) {
} callback = callback || function () {};
function preparePaths(baseTemplatesPaths, callback) {
var coreTemplatesPath = nconf.get('core_templates_path');
var pluginTemplates;
async.waterfall([ async.waterfall([
function (next) { function (next) {
rimraf(viewsPath, next); rimraf(viewsPath, function (err) { next(err); });
}, },
function (next) { function (next) {
mkdirp(viewsPath, next); mkdirp(viewsPath, function (err) { next(err); });
},
function (viewsPath, next) {
plugins.fireHook('static:templates.precompile', {}, next);
}, },
getTemplateDirs,
getTemplateFiles,
function (files, next) {
async.each(Object.keys(files), function (name, next) {
var filePath = files[name];
async.waterfall([
function (next) { function (next) {
plugins.getTemplates(next); fs.readFile(filePath, 'utf8', next);
}, },
function (_pluginTemplates, next) { function (source, next) {
pluginTemplates = _pluginTemplates; processImports(files, name, source, next);
winston.verbose('[meta/templates] Compiling templates');
async.parallel({
coreTpls: function (next) {
file.walk(coreTemplatesPath, next);
}, },
baseThemes: function (next) { function (source, next) {
async.map(baseTemplatesPaths, function (baseTemplatePath, next) { mkdirp(path.join(viewsPath, path.dirname(name)), function (err) {
file.walk(baseTemplatePath, function (err, paths) { next(err, source);
paths = paths.map(function (tpl) {
return {
base: baseTemplatePath,
path: tpl.replace(baseTemplatePath, ''),
};
});
next(err, paths);
}); });
}, next);
}, },
function (compiled, next) {
fs.writeFile(path.join(viewsPath, name), compiled, next);
},
], next);
}, next); }, next);
}, },
function (data, next) { function (next) {
var baseThemes = data.baseThemes; winston.verbose('[meta/templates] Successfully compiled templates.');
var coreTpls = data.coreTpls; next();
var paths = {};
coreTpls.forEach(function (el, i) {
paths[coreTpls[i].replace(coreTemplatesPath, '')] = coreTpls[i];
});
baseThemes.forEach(function (baseTpls) {
baseTpls.forEach(function (el, i) {
paths[baseTpls[i].path] = path.join(baseTpls[i].base, baseTpls[i].path);
});
});
for (var tpl in pluginTemplates) {
if (pluginTemplates.hasOwnProperty(tpl)) {
paths[tpl] = pluginTemplates[tpl];
}
}
next(null, paths);
}, },
], callback); ], callback);
} }
Templates.compile = compile;

@ -138,10 +138,13 @@ Plugins.reloadRoutes = function (callback) {
}); });
}; };
// DEPRECATED: remove in v1.8.0
Plugins.getTemplates = function (callback) { Plugins.getTemplates = function (callback) {
var templates = {}; var templates = {};
var tplName; var tplName;
winston.warn('[deprecated] Plugins.getTemplates is DEPRECATED to be removed in v1.8.0');
Plugins.data.getActive(function (err, plugins) { Plugins.data.getActive(function (err, plugins) {
if (err) { if (err) {
return callback(err); return callback(err);

@ -140,7 +140,7 @@ module.exports = function (Posts) {
db.setObject('topic:' + tid, results.topic, next); db.setObject('topic:' + tid, results.topic, next);
}, },
function (next) { function (next) {
topics.updateTags(tid, data.tags, next); topics.updateTopicTags(tid, data.tags, next);
}, },
function (next) { function (next) {
topics.getTopicTagsObjects(tid, next); topics.getTopicTagsObjects(tid, next);

@ -179,7 +179,7 @@ module.exports = function (Posts) {
return callback(new Error('[[error:self-vote]]')); return callback(new Error('[[error:self-vote]]'));
} }
if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['privileges:downvote'], 10)) { if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['min:rep:downvote'], 10)) {
return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); return callback(new Error('[[error:not-enough-reputation-to-downvote]]'));
} }

@ -200,7 +200,7 @@ module.exports = function (privileges) {
}, next); }, next);
}, },
function (results, next) { function (results, next) {
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0; var minimumReputation = utils.isNumber(meta.config['min:rep:flag']) ? parseInt(meta.config['min:rep:flag'], 10) : 0;
var canFlag = results.isAdminOrMod || parseInt(results.userReputation, 10) >= minimumReputation; var canFlag = results.isAdminOrMod || parseInt(results.userReputation, 10) >= minimumReputation;
next(null, { flag: canFlag }); next(null, { flag: canFlag });
}, },

@ -13,11 +13,19 @@ Tags.create = function (socket, data, callback) {
}; };
Tags.update = function (socket, data, callback) { Tags.update = function (socket, data, callback) {
if (!data) { if (!Array.isArray(data)) {
return callback(new Error('[[error:invalid-data]]'));
}
topics.updateTags(data, callback);
};
Tags.rename = function (socket, data, callback) {
if (!Array.isArray(data)) {
return callback(new Error('[[error:invalid-data]]')); return callback(new Error('[[error:invalid-data]]'));
} }
topics.updateTag(data.tag, data, callback); topics.renameTags(data, callback);
}; };
Tags.deleteTags = function (socket, data, callback) { Tags.deleteTags = function (socket, data, callback) {

@ -9,7 +9,7 @@ var meta = require('../meta');
var _ = require('lodash'); var _ = require('lodash');
var plugins = require('../plugins'); var plugins = require('../plugins');
var utils = require('../utils'); var utils = require('../utils');
var batch = require('../batch');
module.exports = function (Topics) { module.exports = function (Topics) {
Topics.createTags = function (tags, tid, timestamp, callback) { Topics.createTags = function (tags, tid, timestamp, callback) {
@ -96,13 +96,61 @@ module.exports = function (Topics) {
], callback); ], callback);
}; };
Topics.updateTag = function (tag, data, callback) { Topics.updateTags = function (data, callback) {
if (!tag) { async.eachSeries(data, function (tagData, next) {
return setImmediate(callback, new Error('[[error:invalid-tag]]')); db.setObject('tag:' + tagData.value, {
} color: tagData.color,
db.setObject('tag:' + tag, data, callback); bgColor: tagData.bgColor,
}, next);
}, callback);
}; };
Topics.renameTags = function (data, callback) {
async.eachSeries(data, function (tagData, next) {
renameTag(tagData.value, tagData.newName, next);
}, callback);
};
function renameTag(tag, newTagName, callback) {
if (!newTagName || tag === newTagName) {
return setImmediate(callback);
}
async.waterfall([
function (next) {
Topics.createEmptyTag(newTagName, next);
},
function (next) {
batch.processSortedSet('tag:' + tag + ':topics', function (tids, next) {
async.waterfall([
function (next) {
db.sortedSetScores('tag:' + tag + ':topics', tids, next);
},
function (scores, next) {
db.sortedSetAdd('tag:' + newTagName + ':topics', scores, tids, next);
},
function (next) {
var keys = tids.map(function (tid) {
return 'topic:' + tid + ':tags';
});
async.series([
async.apply(db.sortedSetRemove, 'tag:' + tag + ':topics', tids),
async.apply(db.setsRemove, keys, tag),
async.apply(db.setsAdd, keys, newTagName),
], next);
},
], next);
}, next);
},
function (next) {
Topics.deleteTag(tag, next);
},
function (next) {
updateTagCount(newTagName, next);
},
], callback);
}
function updateTagCount(tag, callback) { function updateTagCount(tag, callback) {
callback = callback || function () {}; callback = callback || function () {};
async.waterfall([ async.waterfall([
@ -148,7 +196,9 @@ module.exports = function (Topics) {
return 'tag:' + tag; return 'tag:' + tag;
}), next); }), next);
}, },
], callback); ], function (err) {
callback(err);
});
}; };
function removeTagsFromTopics(tags, callback) { function removeTagsFromTopics(tags, callback) {
@ -266,7 +316,7 @@ module.exports = function (Topics) {
], callback); ], callback);
}; };
Topics.updateTags = function (tid, tags, callback) { Topics.updateTopicTags = function (tid, tags, callback) {
callback = callback || function () {}; callback = callback || function () {};
async.waterfall([ async.waterfall([
function (next) { function (next) {

@ -48,7 +48,7 @@ module.exports = {
done = true; done = true;
return next(); return next();
} }
delete item.expireAt;
if (Object.keys(item).length === 3 && item.hasOwnProperty('_key') && item.hasOwnProperty('value')) { if (Object.keys(item).length === 3 && item.hasOwnProperty('_key') && item.hasOwnProperty('value')) {
client.collection('objects').update({ _key: item._key }, { $rename: { value: 'data' } }, next); client.collection('objects').update({ _key: item._key }, { $rename: { value: 'data' } }, next);
} else { } else {

@ -0,0 +1,25 @@
'use strict';
var db = require('../../database');
module.exports = {
name: 'Rename privileges:downvote and privileges:flag to min:rep:downvote, min:rep:flag respectively',
timestamp: Date.UTC(2018, 0, 12),
method: function (callback) {
db.getObjectFields('config', ['privileges:downvote', 'privileges:flag'], function (err, config) {
if (err) {
return callback(err);
}
db.setObject('config', {
'min:rep:downvote': parseInt(config['privileges:downvote'], 10) || 0,
'min:rep:flag': parseInt(config['privileges:downvote'], 10) || 0,
}, function (err) {
if (err) {
return callback(err);
}
db.deleteObjectFields('config', ['privileges:downvote', 'privileges:flag'], callback);
});
});
},
};

@ -17,14 +17,6 @@ module.exports = function (User) {
var updateUid = data.uid; var updateUid = data.uid;
var oldData; var oldData;
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
}
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
return callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
}
async.waterfall([ async.waterfall([
function (next) { function (next) {
plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next); plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next);
@ -33,13 +25,7 @@ module.exports = function (User) {
fields = data.fields; fields = data.fields;
data = data.data; data = data.data;
async.series([ validateData(uid, data, next);
async.apply(isEmailAvailable, data, updateUid),
async.apply(isUsernameAvailable, data, updateUid),
async.apply(isGroupTitleValid, data),
], function (err) {
next(err);
});
}, },
function (next) { function (next) {
User.getUserFields(updateUid, fields, next); User.getUserFields(updateUid, fields, next);
@ -73,6 +59,19 @@ module.exports = function (User) {
], callback); ], callback);
}; };
function validateData(callerUid, data, callback) {
async.series([
async.apply(isEmailAvailable, data, data.uid),
async.apply(isUsernameAvailable, data, data.uid),
async.apply(isGroupTitleValid, data),
async.apply(isWebsiteValid, callerUid, data),
async.apply(isAboutMeValid, callerUid, data),
async.apply(isSignatureValid, callerUid, data),
], function (err) {
callback(err);
});
}
function isEmailAvailable(data, uid, callback) { function isEmailAvailable(data, uid, callback) {
if (!data.email) { if (!data.email) {
return callback(); return callback();
@ -141,6 +140,52 @@ module.exports = function (User) {
} }
} }
function isWebsiteValid(callerUid, data, callback) {
if (!data.website) {
return setImmediate(callback);
}
checkMinReputation(callerUid, data.uid, 'min:rep:website', callback);
}
function isAboutMeValid(callerUid, data, callback) {
if (!data.aboutme) {
return setImmediate(callback);
}
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
}
checkMinReputation(callerUid, data.uid, 'min:rep:aboutme', callback);
}
function isSignatureValid(callerUid, data, callback) {
if (!data.signature) {
return setImmediate(callback);
}
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
return callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
}
checkMinReputation(callerUid, data.uid, 'min:rep:signature', callback);
}
function checkMinReputation(callerUid, uid, setting, callback) {
var isSelf = parseInt(callerUid, 10) === parseInt(uid, 10);
if (!isSelf) {
return setImmediate(callback);
}
async.waterfall([
function (next) {
User.getUserField(uid, 'reputation', next);
},
function (reputation, next) {
if (parseInt(reputation, 10) < (parseInt(meta.config[setting], 10) || 0)) {
return next(new Error('[[error:not-enough-reputation-' + setting.replace(/:/g, '-') + ']]'));
}
next();
},
], callback);
}
function updateEmail(uid, newEmail, callback) { function updateEmail(uid, newEmail, callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {

@ -41,6 +41,7 @@
<p>[[admin/manage/tags:description]]</p> <p>[[admin/manage/tags:description]]</p>
<button class="btn btn-primary btn-block" id="create">[[admin/manage/tags:create]]</button> <button class="btn btn-primary btn-block" id="create">[[admin/manage/tags:create]]</button>
<button class="btn btn-primary btn-block" id="modify">[[admin/manage/tags:modify]]</button> <button class="btn btn-primary btn-block" id="modify">[[admin/manage/tags:modify]]</button>
<button class="btn btn-primary btn-block" id="rename">[[admin/manage/tags:rename]]</button>
<button class="btn btn-warning btn-block" id="deleteSelected">[[admin/manage/tags:delete]]</button> <button class="btn btn-warning btn-block" id="deleteSelected">[[admin/manage/tags:delete]]</button>
</div> </div>
</div> </div>
@ -74,4 +75,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="rename-modal hidden">
<div class="form-group">
<label for="value">[[admin/manage/tags:name]]</label>
<input id="value" data-name="value" value="{tags.value}" class="form-control" />
</div>
</div>
</div> </div>

@ -32,8 +32,11 @@
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/reputation:thresholds]]</div> <div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/reputation:thresholds]]</div>
<div class="col-sm-10 col-xs-12"> <div class="col-sm-10 col-xs-12">
<form> <form>
<strong>[[admin/settings/reputation:min-rep-downvote]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="privileges:downvote"><br /> <strong>[[admin/settings/reputation:min-rep-downvote]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:downvote"><br />
<strong>[[admin/settings/reputation:min-rep-flag]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="privileges:flag"><br /> <strong>[[admin/settings/reputation:min-rep-flag]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:flag"><br />
<strong>[[admin/settings/reputation:min-rep-website]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:website"><br />
<strong>[[admin/settings/reputation:min-rep-aboutme]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:aboutme"><br />
<strong>[[admin/settings/reputation:min-rep-signature]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:signature"><br />
</form> </form>
</div> </div>
</div> </div>

@ -591,21 +591,21 @@ describe('Admin Controllers', function () {
it('should error with not enough reputation to flag', function (done) { it('should error with not enough reputation to flag', function (done) {
var socketFlags = require('../src/socket.io/flags'); var socketFlags = require('../src/socket.io/flags');
var oldValue = meta.config['privileges:flag']; var oldValue = meta.config['min:rep:flag'];
meta.config['privileges:flag'] = 1000; meta.config['min:rep:flag'] = 1000;
socketFlags.create({ uid: regularUid }, { id: pid, type: 'post', reason: 'spam' }, function (err) { socketFlags.create({ uid: regularUid }, { id: pid, type: 'post', reason: 'spam' }, function (err) {
assert.equal(err.message, '[[error:not-enough-reputation-to-flag]]'); assert.equal(err.message, '[[error:not-enough-reputation-to-flag]]');
meta.config['privileges:flag'] = oldValue; meta.config['min:rep:flag'] = oldValue;
done(); done();
}); });
}); });
it('should return flag details', function (done) { it('should return flag details', function (done) {
var socketFlags = require('../src/socket.io/flags'); var socketFlags = require('../src/socket.io/flags');
var oldValue = meta.config['privileges:flag']; var oldValue = meta.config['min:rep:flag'];
meta.config['privileges:flag'] = 0; meta.config['min:rep:flag'] = 0;
socketFlags.create({ uid: regularUid }, { id: pid, type: 'post', reason: 'spam' }, function (err, data) { socketFlags.create({ uid: regularUid }, { id: pid, type: 'post', reason: 'spam' }, function (err, data) {
meta.config['privileges:flag'] = oldValue; meta.config['min:rep:flag'] = oldValue;
assert.ifError(err); assert.ifError(err);
request(nconf.get('url') + '/api/flags/' + data.flagId, { jar: moderatorJar, json: true }, function (err, res, body) { request(nconf.get('url') + '/api/flags/' + data.flagId, { jar: moderatorJar, json: true }, function (err, res, body) {
assert.ifError(err); assert.ifError(err);

@ -364,7 +364,7 @@ describe('Flags', function () {
}); });
it('should not pass validation if flag threshold is set and user rep does not meet it', function (done) { it('should not pass validation if flag threshold is set and user rep does not meet it', function (done) {
Meta.configs.set('privileges:flag', '50', function (err) { Meta.configs.set('min:rep:flag', '50', function (err) {
assert.ifError(err); assert.ifError(err);
Flags.validate({ Flags.validate({
@ -374,7 +374,7 @@ describe('Flags', function () {
}, function (err) { }, function (err) {
assert.ok(err); assert.ok(err);
assert.strictEqual('[[error:not-enough-reputation-to-flag]]', err.message); assert.strictEqual('[[error:not-enough-reputation-to-flag]]', err.message);
Meta.configs.set('privileges:flag', 0, done); Meta.configs.set('min:rep:flag', 0, done);
}); });
}); });
}); });

@ -1350,22 +1350,22 @@ describe('Topic\'s', function () {
}); });
}); });
it('should error if data.tag is invalid', function (done) { it('should error if data is not an array', function (done) {
socketAdmin.tags.update({ uid: adminUid }, { socketAdmin.tags.update({ uid: adminUid }, {
bgColor: '#ff0000', bgColor: '#ff0000',
color: '#00ff00', color: '#00ff00',
}, function (err) { }, function (err) {
assert.equal(err.message, '[[error:invalid-tag]]'); assert.equal(err.message, '[[error:invalid-data]]');
done(); done();
}); });
}); });
it('should update tag', function (done) { it('should update tag', function (done) {
socketAdmin.tags.update({ uid: adminUid }, { socketAdmin.tags.update({ uid: adminUid }, [{
tag: 'emptytag', value: 'emptytag',
bgColor: '#ff0000', bgColor: '#ff0000',
color: '#00ff00', color: '#00ff00',
}, function (err) { }], function (err) {
assert.ifError(err); assert.ifError(err);
db.getObject('tag:emptytag', function (err, data) { db.getObject('tag:emptytag', function (err, data) {
assert.ifError(err); assert.ifError(err);
@ -1376,6 +1376,35 @@ describe('Topic\'s', function () {
}); });
}); });
it('should rename tags', function (done) {
async.parallel({
topic1: function (next) {
topics.post({ uid: adminUid, tags: ['plugins'], title: 'topic tagged with plugins', content: 'topic 1 content', cid: topic.categoryId }, next);
},
topic2: function (next) {
topics.post({ uid: adminUid, tags: ['plugin'], title: 'topic tagged with plugin', content: 'topic 2 content', cid: topic.categoryId }, next);
},
}, function (err, result) {
assert.ifError(err);
socketAdmin.tags.rename({ uid: adminUid }, [{
value: 'plugin',
newName: 'plugins',
}], function (err) {
assert.ifError(err);
topics.getTagTids('plugins', 0, -1, function (err, tids) {
assert.ifError(err);
assert.equal(tids.length, 2);
topics.getTopicTags(result.topic2.topicData.tid, function (err, tags) {
assert.ifError(err);
assert.equal(tags.length, 1);
assert.equal(tags[0], 'plugins');
done();
});
});
});
});
});
it('should return related topics', function (done) { it('should return related topics', function (done) {
var meta = require('../src/meta'); var meta = require('../src/meta');
meta.config.maximumRelatedTopics = 2; meta.config.maximumRelatedTopics = 2;

Loading…
Cancel
Save