diff --git a/install/data/defaults.json b/install/data/defaults.json
index 9e08416ce6..3d45ff6565 100644
--- a/install/data/defaults.json
+++ b/install/data/defaults.json
@@ -38,5 +38,5 @@
"bookmarkThreshold": 5,
"topicsPerList": 20,
"autoDetectLang": 1,
- "privileges:flag": 0
+ "min:rep:flag": 0
}
diff --git a/install/package.json b/install/package.json
index f3538ddbcf..e06f51139e 100644
--- a/install/package.json
+++ b/install/package.json
@@ -69,9 +69,9 @@
"nodebb-plugin-spam-be-gone": "0.5.1",
"nodebb-rewards-essentials": "0.0.9",
"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-vanilla": "8.1.5",
+ "nodebb-theme-vanilla": "8.1.7",
"nodebb-widget-essentials": "4.0.1",
"nodemailer": "4.4.1",
"passport": "^0.4.0",
diff --git a/install/web.js b/install/web.js
index 92dcdb17d3..8bfd416785 100644
--- a/install/web.js
+++ b/install/web.js
@@ -10,6 +10,7 @@ var less = require('less');
var async = require('async');
var uglify = require('uglify-js');
var nconf = require('nconf');
+var _ = require('lodash');
var Benchpress = require('benchpressjs');
var app = express();
@@ -103,14 +104,16 @@ function welcome(req, res) {
}
function install(req, res) {
+ req.setTimeout(0);
+ var setupEnvVars = _.assign({}, process.env);
for (var i in req.body) {
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'], {
- env: process.env,
+ env: setupEnvVars,
});
child.on('close', function (data) {
diff --git a/public/language/de/admin/general/homepage.json b/public/language/de/admin/general/homepage.json
index fe3bdac766..186e89ae62 100644
--- a/public/language/de/admin/general/homepage.json
+++ b/public/language/de/admin/general/homepage.json
@@ -4,5 +4,5 @@
"home-page-route": "Startseitenpfad",
"custom-route": "Eigener Startseitenpfad",
"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\")"
}
\ No newline at end of file
diff --git a/public/language/de/admin/manage/users.json b/public/language/de/admin/manage/users.json
index 58990723c8..e4857f610d 100644
--- a/public/language/de/admin/manage/users.json
+++ b/public/language/de/admin/manage/users.json
@@ -27,8 +27,8 @@
"pills.banned": "Gebannt",
"pills.search": "Benutzer Suche",
- "search.uid": "By User ID",
- "search.uid-placeholder": "Enter a user ID to search",
+ "search.uid": "Nach Benutzer-ID",
+ "search.uid-placeholder": "Gib eine Benutzer-ID ein um danach zu suchen",
"search.username": "Nach Nutzernamen",
"search.username-placeholder": "Einen Nutzernamen eingeben, um danach zu suchen",
"search.email": "Nach E-Mail",
@@ -71,15 +71,15 @@
"alerts.lockout-reset-success": "Ausschlüsse zurückgesetzt",
"alerts.flag-reset-success": "Meldung(en) zurückgesetzt!",
"alerts.no-remove-yourself-admin": "Du kannst dich nicht selbst als Administrator degradieren!",
- "alerts.make-admin-success": "User is now administrator.",
- "alerts.confirm-remove-admin": "Do you really want to remove this administrator?",
- "alerts.remove-admin-success": "User is no longer administrator.",
- "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.remove-global-mod-success": "User is no longer global noderator",
- "alerts.make-moderator-success": "User is now moderator.",
- "alerts.confirm-remove-moderator": "Do you really want to remove this moderator?",
- "alerts.remove-moderator-success": "User is no longer moderator.",
+ "alerts.make-admin-success": "Der Benutzer ist nun ein Administrator",
+ "alerts.confirm-remove-admin": "Willst du wirklich diesen Administrator entfernen?",
+ "alerts.remove-admin-success": "Der Benutzer ist kein Administrator mehr",
+ "alerts.make-global-mod-success": "Der Benutzer ist nun ein globaler Moderator",
+ "alerts.confirm-remove-global-mod": "Willst du wirklich diesen globalen Moderator entfernen?",
+ "alerts.remove-global-mod-success": "Der Benutzer ist kein globaler Moderator mehr",
+ "alerts.make-moderator-success": "Der Benutzer ist nun ein Moderator",
+ "alerts.confirm-remove-moderator": "Willst du wirklich diesen Moderator entfernen?",
+ "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.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?",
diff --git a/public/language/de/admin/menu.json b/public/language/de/admin/menu.json
index af15dfed0d..748b72092a 100644
--- a/public/language/de/admin/menu.json
+++ b/public/language/de/admin/menu.json
@@ -9,7 +9,7 @@
"section-manage": "Verwalten",
"manage/categories": "Kategorien",
- "manage/privileges": "Privileges",
+ "manage/privileges": "Privilegien",
"manage/tags": "Tags",
"manage/users": "Benutzer",
"manage/admins-mods": "Admins & Mods",
diff --git a/public/language/de/admin/settings/chat.json b/public/language/de/admin/settings/chat.json
index 8c7acb76e6..d80d8fb788 100644
--- a/public/language/de/admin/settings/chat.json
+++ b/public/language/de/admin/settings/chat.json
@@ -6,6 +6,6 @@
"max-length": "Maximale Chatnachrichtenlänge",
"max-room-size": "Maximale Anzahl an Nutzern pro Chat-Room",
"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-delete-after": "Number of seconds before users are allowed to delete 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": "Zeit in Sekunden bevor Benutzer Chat-Nachrichten löschen dürfen, nachdem sie erstellt wurden. (0 bedeutet deaktiviert)"
}
\ No newline at end of file
diff --git a/public/language/de/admin/settings/post.json b/public/language/de/admin/settings/post.json
index ebfa7b0001..d43fe27f77 100644
--- a/public/language/de/admin/settings/post.json
+++ b/public/language/de/admin/settings/post.json
@@ -6,25 +6,25 @@
"sorting.most-votes": "Meiste Bewertungen",
"sorting.most-posts": "Meiste Beiträge",
"sorting.topic-default": "Standardmäßige Themensortierung",
- "length": "Post Length",
+ "length": "Beitragslänge",
"restrictions": "Posting beschränkungen",
- "restrictions-new": "New User Restrictions",
+ "restrictions-new": "Beschränkungen für neue Benutzer",
"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-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users.",
- "restrictions.seconds-between": "Seconds between posts",
- "restrictions.seconds-between-new": "Seconds between posts for new users",
- "restrictions.rep-threshold": "Reputation threshold before these restrictions are lifted",
+ "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": "Sekunden zwischen Beiträgen",
+ "restrictions.seconds-between-new": "Sekunden zwischen Beiträgen für neue Benutzer",
+ "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-edit-after": "Number of seconds before users are allowed to edit posts after posting. (0 disabled)",
- "restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete 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": "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.min-title-length": "Minimale Titellänge",
"restrictions.max-title-length": "Maximale Titellänge",
"restrictions.min-post-length": "Minimale 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",
"timestamp": "Zeitstempel",
"timestamp.cut-off": "Tageslimit für Relative Zeitangaben (in Tagen)",
diff --git a/public/language/de/error.json b/public/language/de/error.json
index 5686c49861..ddd24e8144 100644
--- a/public/language/de/error.json
+++ b/public/language/de/error.json
@@ -17,7 +17,7 @@
"invalid-login-credentials": "Ungültige Zugangsdaten",
"invalid-username-or-password": "Bitte gib sowohl einen Benutzernamen als auch ein Passwort an",
"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",
"invalid-pagination-value": "Ungültige Seitennummerierung, muss mindestens %1 und maximal %2 sein",
"username-taken": "Der Benutzername ist bereits vergeben",
@@ -114,8 +114,8 @@
"cant-edit-chat-message": "Du darfst diese Nachricht nicht ändern",
"cant-remove-last-user": "Du kannst den letzten Benutzer nicht entfernen",
"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-delete-duration-expired": "You are only allowed to delete 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": "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.",
"reputation-system-disabled": "Das Reputationssystem ist deaktiviert.",
"downvoting-disabled": "Downvotes sind deaktiviert.",
diff --git a/public/language/de/global.json b/public/language/de/global.json
index b2e8905faf..e2c9e36742 100644
--- a/public/language/de/global.json
+++ b/public/language/de/global.json
@@ -53,7 +53,7 @@
"topics": "Themen",
"posts": "Beiträge",
"best": "Bestbewertet",
- "votes": "Votes",
+ "votes": "Stimmen",
"upvoters": "Upvoter",
"upvoted": "Positiv bewertet",
"downvoters": "Downvoter",
diff --git a/public/language/de/pages.json b/public/language/de/pages.json
index 042e854383..a8ddb4f7ae 100644
--- a/public/language/de/pages.json
+++ b/public/language/de/pages.json
@@ -6,7 +6,7 @@
"popular-month": "Beliebte Themen dieses Monats",
"popular-alltime": "Beliebteste Themen",
"recent": "Neueste Themen",
- "top": "Top Voted Topics",
+ "top": "Bestbewertetste Themen",
"moderator-tools": "Moderator-Werkzeuge",
"flagged-content": "Gemeldeter Inhalt",
"ip-blacklist": "IP Blacklist",
diff --git a/public/language/en-GB/admin/appearance/customise.json b/public/language/en-GB/admin/appearance/customise.json
index a1220ec96d..56c11a2805 100644
--- a/public/language/en-GB/admin/appearance/customise.json
+++ b/public/language/en-GB/admin/appearance/customise.json
@@ -1,7 +1,7 @@
{
- "custom-css": "Custom CSS",
- "custom-css.description": "Enter your own CSS declarations here, which will be applied after all other styles.",
- "custom-css.enable": "Enable Custom CSS",
+ "custom-css": "Custom CSS/LESS",
+ "custom-css.description": "Enter your own CSS/LESS declarations here, which will be applied after all other styles.",
+ "custom-css.enable": "Enable Custom CSS/LESS",
"custom-js": "Custom Javascript",
"custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.",
diff --git a/public/language/en-GB/admin/manage/tags.json b/public/language/en-GB/admin/manage/tags.json
index db40e9f098..df597a6166 100644
--- a/public/language/en-GB/admin/manage/tags.json
+++ b/public/language/en-GB/admin/manage/tags.json
@@ -6,6 +6,7 @@
"description": "Select tags via clicking and/or dragging, use shift to select multiple.",
"create": "Create Tag",
"modify": "Modify Tags",
+ "rename": "Rename Tags",
"delete": "Delete Selected Tags",
"search": "Search for tags...",
"settings": "Click here to visit the tag settings page.",
diff --git a/public/language/en-GB/admin/settings/reputation.json b/public/language/en-GB/admin/settings/reputation.json
index f0e59e8db9..c698592cff 100644
--- a/public/language/en-GB/admin/settings/reputation.json
+++ b/public/language/en-GB/admin/settings/reputation.json
@@ -5,5 +5,8 @@
"votes-are-public": "All Votes Are Public",
"thresholds": "Activity Thresholds",
"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"
}
\ No newline at end of file
diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json
index 0cb282a8d4..8816e253be 100644
--- a/public/language/en-GB/error.json
+++ b/public/language/en-GB/error.json
@@ -148,6 +148,9 @@
"downvoting-disabled": "Downvoting is disabled",
"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-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",
"self-vote": "You cannot vote on your own post",
diff --git a/public/language/fr/admin/general/dashboard.json b/public/language/fr/admin/general/dashboard.json
index fbc878c950..6bb5c222e3 100644
--- a/public/language/fr/admin/general/dashboard.json
+++ b/public/language/fr/admin/general/dashboard.json
@@ -31,8 +31,8 @@
"notices": "Informations",
"restart-not-required": "Pas de redémarrage nécessaire",
"restart-required": "Redémarrage requis",
- "search-plugin-installed": "Recherche un plugin installé",
- "search-plugin-not-installed": "Rechercher un plugin non installé",
+ "search-plugin-installed": "Le plugin de recherche est 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",
"control-panel": "Contrôle du système",
diff --git a/public/language/sr/error.json b/public/language/sr/error.json
index f99bb32e55..3ec59d1194 100644
--- a/public/language/sr/error.json
+++ b/public/language/sr/error.json
@@ -17,7 +17,7 @@
"invalid-login-credentials": "Неважећи акредитиви за пријављивање",
"invalid-username-or-password": "Молимо наведите и корисничко име и лозинку",
"invalid-search-term": "Неисправан упит за претрагу",
- "invalid-url": "Invalid URL",
+ "invalid-url": "Неважећа адреса",
"csrf-invalid": "Нисмо успели да вас пријавимо, вероватно због истека сесије. Молимо покушајте поново",
"invalid-pagination-value": "Неважећа вредност приликом нумерисања страница, мора бити најмање %1 а највише %2 ",
"username-taken": "Корисничко име је заузето",
@@ -114,8 +114,8 @@
"cant-edit-chat-message": "Није вам дозвољено да уређујете ову поруку",
"cant-remove-last-user": "Не можете уклонити последњег корисника",
"cant-delete-chat-message": "Није вам дозвољено да избришете ову поруку",
- "chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting",
- "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting",
+ "chat-edit-duration-expired": "Време у којем вам је дозвољено уређивање порука ћаскања након објављивања: %1 сек.",
+ "chat-delete-duration-expired": "Време у којем вам је дозвољено брисање порука ћаскања након објављивања: %1 сек.",
"already-voting-for-this-post": "Већ сте гласали за ову поруку.",
"reputation-system-disabled": "Угледи су онемогућени.",
"downvoting-disabled": "Негативно гласање је онемогућено",
diff --git a/public/language/sr/global.json b/public/language/sr/global.json
index 7f1ee9c5f7..1742fe768c 100644
--- a/public/language/sr/global.json
+++ b/public/language/sr/global.json
@@ -53,7 +53,7 @@
"topics": "Теме",
"posts": "Поруке",
"best": "Најбоље",
- "votes": "Votes",
+ "votes": "Гласови",
"upvoters": "Позитивно гласали",
"upvoted": "Позитивно гласано",
"downvoters": "Негативно гласали",
diff --git a/public/language/sr/pages.json b/public/language/sr/pages.json
index 24d60058ce..b7391c0af5 100644
--- a/public/language/sr/pages.json
+++ b/public/language/sr/pages.json
@@ -6,7 +6,7 @@
"popular-month": "Популарне теме овог месеца",
"popular-alltime": "Популарне теме свих времена",
"recent": "Недавне теме",
- "top": "Top Voted Topics",
+ "top": "Најгласаније теме",
"moderator-tools": "Алати модератора",
"flagged-content": "Садржај означен заставицом",
"ip-blacklist": "Црна листа IP адреса",
diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json
index 35a41259f7..44766303f3 100644
--- a/public/language/sr/topic.json
+++ b/public/language/sr/topic.json
@@ -52,7 +52,7 @@
"not-watching.description": "Немој ме обавештавати о новим одговорима.
Прикажи тему у непрочитаним ако категорија није игнорисана.",
"ignoring.description": "Немој ме обавештавати о новим одговорима.
Не приказуј тему у непрочитаним",
"thread_tools.title": "Алатке теме",
- "thread_tools.markAsUnreadForAll": "Mark Unread For All",
+ "thread_tools.markAsUnreadForAll": "Означи као непрочитано за све",
"thread_tools.pin": "Закачи тему",
"thread_tools.unpin": "Откачи тему",
"thread_tools.lock": "Закључај тему",
diff --git a/public/language/tr/email.json b/public/language/tr/email.json
index 71077d5cc7..dcb4e12250 100644
--- a/public/language/tr/email.json
+++ b/public/language/tr/email.json
@@ -30,7 +30,7 @@
"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.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.",
"unsub.cta": "Buraya tıklayarak ayarlarınızı değiştirebilirsiniz.",
"banned.subject": "%1 'den yasaklandınız",
diff --git a/public/language/tr/global.json b/public/language/tr/global.json
index b406e423d1..2600f7683e 100644
--- a/public/language/tr/global.json
+++ b/public/language/tr/global.json
@@ -53,7 +53,7 @@
"topics": "Başlık",
"posts": "İleti",
"best": "En İyi",
- "votes": "Votes",
+ "votes": "Oy",
"upvoters": "Artı",
"upvoted": "Artı",
"downvoters": "Eksi",
diff --git a/public/language/tr/pages.json b/public/language/tr/pages.json
index 175e6c5a45..5056f72773 100644
--- a/public/language/tr/pages.json
+++ b/public/language/tr/pages.json
@@ -6,7 +6,7 @@
"popular-month": "Bu ayki popüler başlıklar",
"popular-alltime": "En popüler başlıklar",
"recent": "Güncel Konular",
- "top": "Top Voted Topics",
+ "top": "En Çok Oylanan Başlıklar",
"moderator-tools": "Moderatör Araçları",
"flagged-content": "Bayraklanan İçerik",
"ip-blacklist": "IP Kara Listesi",
@@ -45,7 +45,7 @@
"account/bookmarks": "%1'in yer imine eklenmiş iletiler",
"account/settings": "Kullanıcı Ayarları",
"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/downvoted": "%1 tarafından eksilenen gönderiler",
"account/best": "%1 tarafından en iyi gönderiler",
diff --git a/public/language/tr/user.json b/public/language/tr/user.json
index 6a0e1c2685..477dd4d27e 100644
--- a/public/language/tr/user.json
+++ b/public/language/tr/user.json
@@ -85,7 +85,7 @@
"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_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_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.",
@@ -101,11 +101,11 @@
"outgoing-message-sound": "Giden ileti sesi",
"notification-sound": "Bildirim sesi",
"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.everyTen": "Every Ten Upvotes",
- "upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
- "upvote-notif-freq.disabled": "Disabled",
+ "upvote-notif-freq.everyTen": "Her Artı On Oy",
+ "upvote-notif-freq.logarithmic": "10, 100, 1000...",
+ "upvote-notif-freq.disabled": "Devre dışı",
"browsing": "Tarayıcı Ayaları",
"open_links_in_new_tab": "Dışarı giden bağlantıları yeni sekmede aç",
"enable_topic_searching": "Konu içi aramayı aktive et",
@@ -126,9 +126,9 @@
"sso.title": "Tek giriş servisleri",
"sso.associated": "Birleştirilmiş",
"sso.not-associated": "Birleştirmek için buraya tıklayın",
- "sso.dissociate": "Dissociate",
- "sso.dissociate-confirm-title": "Confirm Dissociation",
- "sso.dissociate-confirm": "Are you sure you wish to dissociate your account from %1?",
+ "sso.dissociate": "Ayrış",
+ "sso.dissociate-confirm-title": "Ayrışmayı Onayla",
+ "sso.dissociate-confirm": "%1 'den ayrışmak istediğinizden emin misiniz?",
"info.latest-flags": "Son Bayraklar",
"info.no-flags": "Hiç bayraklanan bir ileti bulunamadı",
"info.ban-history": "Yasaklama Olayları",
diff --git a/public/language/zh-CN/admin/general/homepage.json b/public/language/zh-CN/admin/general/homepage.json
index 95cf29899f..8864e4eb34 100644
--- a/public/language/zh-CN/admin/general/homepage.json
+++ b/public/language/zh-CN/admin/general/homepage.json
@@ -4,5 +4,5 @@
"home-page-route": "主页路由",
"custom-route": "自定义路由",
"allow-user-home-pages": "允许用户主页",
- "home-page-title": "Title of the home page (default \"Home\")"
+ "home-page-title": "首页标题(默认“Home”)"
}
\ No newline at end of file
diff --git a/public/language/zh-CN/admin/manage/users.json b/public/language/zh-CN/admin/manage/users.json
index 45ad017b77..4538b91287 100644
--- a/public/language/zh-CN/admin/manage/users.json
+++ b/public/language/zh-CN/admin/manage/users.json
@@ -27,8 +27,8 @@
"pills.banned": "被封禁",
"pills.search": "搜寻用户",
- "search.uid": "By User ID",
- "search.uid-placeholder": "Enter a user ID to search",
+ "search.uid": "通过用户名",
+ "search.uid-placeholder": "搜索用户ID",
"search.username": "通过用户名",
"search.username-placeholder": "输入你想找的用户名",
"search.email": "通过邮箱",
@@ -71,9 +71,9 @@
"alerts.lockout-reset-success": "闭锁已重置!",
"alerts.flag-reset-success": "举报已重置!",
"alerts.no-remove-yourself-admin": "你无法撤销自己的管理员身份!",
- "alerts.make-admin-success": "User is now administrator.",
- "alerts.confirm-remove-admin": "Do you really want to remove this administrator?",
- "alerts.remove-admin-success": "User is no longer administrator.",
+ "alerts.make-admin-success": "用户已成为管理员",
+ "alerts.confirm-remove-admin": "真的想要删除这个管理员?",
+ "alerts.remove-admin-success": "此用户不再是管理员",
"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.remove-global-mod-success": "User is no longer global noderator",
diff --git a/public/src/admin/appearance/customise.js b/public/src/admin/appearance/customise.js
index 86894f0d63..70393a50ae 100644
--- a/public/src/admin/appearance/customise.js
+++ b/public/src/admin/appearance/customise.js
@@ -14,7 +14,7 @@ define('admin/appearance/customise', ['admin/settings', 'ace/ace'], function (Se
var customHTML = ace.edit('customHTML');
customCSS.setTheme('ace/theme/twilight');
- customCSS.getSession().setMode('ace/mode/css');
+ customCSS.getSession().setMode('ace/mode/less');
customCSS.on('change', function () {
app.flags = app.flags || {};
diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js
index 717d4bba1d..de67b86743 100644
--- a/public/src/admin/manage/tags.js
+++ b/public/src/admin/manage/tags.js
@@ -15,6 +15,7 @@ define('admin/manage/tags', [
handleCreate();
handleSearch();
handleModify();
+ handleRename();
handleDeleteSelected();
};
@@ -103,15 +104,25 @@ define('admin/manage/tags', [
var modal = $('.bootbox');
var bgColor = modal.find('[data-name="bgColor"]').val();
var color = modal.find('[data-name="color"]').val();
-
+ var data = [];
tagsToModify.each(function (idx, 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="color"]').val(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() {
$('#deleteSelected').on('click', function () {
var tagsToDelete = $('.tag-row.ui-selected');
@@ -158,21 +209,5 @@ define('admin/manage/tags', [
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;
});
diff --git a/public/src/client/account/edit/email.js b/public/src/client/account/edit/email.js
index e1a068979a..ae182029f0 100644
--- a/public/src/client/account/edit/email.js
+++ b/public/src/client/account/edit/email.js
@@ -31,7 +31,7 @@ define('forum/account/edit/email', ['forum/account/header'], function (header) {
return app.alertError(err.message);
}
- ajaxify.go('user/' + ajaxify.data.userslug);
+ ajaxify.go('user/' + ajaxify.data.userslug + '/edit');
});
return false;
diff --git a/public/src/client/account/edit/password.js b/public/src/client/account/edit/password.js
index 1585a85577..6aa66a13d8 100644
--- a/public/src/client/account/edit/password.js
+++ b/public/src/client/account/edit/password.js
@@ -82,8 +82,11 @@ define('forum/account/edit/password', ['forum/account/header', 'translator', 'zx
onPasswordConfirmChanged();
return app.alertError(err.message);
}
-
- window.location.href = config.relative_path + '/login';
+ if (parseInt(app.user.uid, 10) === parseInt(ajaxify.data.uid, 10)) {
+ window.location.href = config.relative_path + '/login';
+ } else {
+ ajaxify.go('user/' + ajaxify.data.userslug + '/edit');
+ }
});
} else {
if (!passwordsmatch) {
diff --git a/public/src/client/account/edit/username.js b/public/src/client/account/edit/username.js
index f00e4d16fe..6178466f24 100644
--- a/public/src/client/account/edit/username.js
+++ b/public/src/client/account/edit/username.js
@@ -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']);
}
- ajaxify.go('user/' + userslug);
+ ajaxify.go('user/' + userslug + '/edit');
});
return false;
diff --git a/public/src/utils.js b/public/src/utils.js
index 0e300d2772..2fcdfa8157 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -545,6 +545,20 @@
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 () {
// http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser
var nua = navigator.userAgent;
@@ -732,6 +746,27 @@
rtrim: function (str) {
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" */
diff --git a/public/src/widgets.js b/public/src/widgets.js
index badc55c892..8123e18103 100644
--- a/public/src/widgets.js
+++ b/public/src/widgets.js
@@ -58,6 +58,7 @@
title: $(this).attr('title'),
});
});
+ utils.addNoReferrer(widgetAreas);
$(window).trigger('action:widgets.loaded', {});
callback();
};
diff --git a/src/categories.js b/src/categories.js
index 452762ae38..ab1d277e68 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -339,10 +339,6 @@ Categories.buildForSelect = function (uid, privilege, callback) {
Categories.buildForSelectCategories = function (categories, callback) {
function recursive(category, categoriesData, level, depth) {
- if (category.link) {
- return;
- }
-
var bullet = level ? '• ' : '';
category.value = category.cid;
category.level = level;
@@ -358,7 +354,7 @@ Categories.buildForSelectCategories = function (categories, callback) {
var categoriesData = [];
categories = categories.filter(function (category) {
- return category && !category.link && !parseInt(category.parentCid, 10);
+ return category && !parseInt(category.parentCid, 10);
});
categories.forEach(function (category) {
diff --git a/src/cli/index.js b/src/cli/index.js
index 07efaadf5e..e4679a4e19 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -8,9 +8,9 @@ var dirname = require('./paths').baseDir;
// check to make sure dependencies are installed
try {
- require('../../package.json');
+ fs.accessSync(path.join(dirname, 'package.json'), fs.constants.R_OK);
} catch (e) {
- if (e.code === 'MODULE_NOT_FOUND') {
+ if (e.code === 'ENOENT') {
console.warn('package.json not found.');
console.log('Populating package.json...');
@@ -18,6 +18,8 @@ try {
packageInstall.preserveExtraneousPlugins();
try {
+ fs.accessSync(path.join(dirname, 'node_modules/colors/package.json'), fs.constants.R_OK);
+
require('colors');
console.log('OK'.green);
} catch (e) {
@@ -29,6 +31,8 @@ try {
}
try {
+ fs.accessSync(path.join(dirname, 'node_modules/semver/package.json'), fs.constants.R_OK);
+
var semver = require('semver');
var defaultPackage = require('../../install/package.json');
@@ -50,6 +54,7 @@ try {
console.warn('Dependencies outdated or not yet installed.');
console.log('Installing them now...\n');
+ packageInstall.updatePackageFile();
packageInstall.installAll();
require('colors');
@@ -292,16 +297,12 @@ program
}
});
-program
- .command('*', {}, {
- noHelp: true,
- })
- .action(function () {
- program.help();
- });
-
require('./colors');
+if (process.argv.length === 2) {
+ program.help();
+}
+
program.executables = false;
program.parse(process.argv);
diff --git a/src/cli/package-install.js b/src/cli/package-install.js
index 1892a48cfc..af245b08fb 100644
--- a/src/cli/package-install.js
+++ b/src/cli/package-install.js
@@ -33,6 +33,7 @@ function installAll() {
var prod = global.env !== 'development';
var command = 'npm install';
try {
+ fs.accessSync(path.join(modulesPath, 'nconf/package.json'), fs.constants.R_OK);
var packageManager = require('nconf').get('package_manager');
if (packageManager === 'yarn') {
command = 'yarn';
diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js
index b5bd4cf3a5..5e40b83ef2 100644
--- a/src/controllers/accounts/edit.js
+++ b/src/controllers/accounts/edit.js
@@ -28,6 +28,9 @@ editController.get = function (req, res, callback) {
userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10);
userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads, 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.defaultAvatar = user.getDefaultAvatar();
diff --git a/src/controllers/mods.js b/src/controllers/mods.js
index a7cb252c2c..69bfd7fbbf 100644
--- a/src/controllers/mods.js
+++ b/src/controllers/mods.js
@@ -126,7 +126,7 @@ modsController.flags.detail = function (req, res, next) {
assignees: results.assignees,
type_bool: ['post', 'user', 'empty'].reduce(function (memo, cur) {
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 {
memo[cur] = !Object.keys(results.flagData.target).length;
}
diff --git a/src/controllers/search.js b/src/controllers/search.js
index f02a61dc31..49e762407a 100644
--- a/src/controllers/search.js
+++ b/src/controllers/search.js
@@ -54,6 +54,10 @@ searchController.search = function (req, res, next) {
return next(err);
}
+ results.categories = results.categories.filter(function (category) {
+ return category && !category.link;
+ });
+
var categoriesData = [
{ value: 'all', text: '[[unread:all_categories]]' },
{ value: 'watched', text: '[[category:watched-categories]]' },
diff --git a/src/database/redis.js b/src/database/redis.js
index e45f8f0ef8..70a8ffbd53 100644
--- a/src/database/redis.js
+++ b/src/database/redis.js
@@ -37,19 +37,22 @@ redisModule.questions = [
];
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;
+
+ require('./redis/main')(redisClient, redisModule);
+ require('./redis/hash')(redisClient, redisModule);
+ require('./redis/sets')(redisClient, redisModule);
+ require('./redis/sorted')(redisClient, redisModule);
+ require('./redis/list')(redisClient, redisModule);
- redisModule.client = redisClient;
-
- require('./redis/main')(redisClient, redisModule);
- require('./redis/hash')(redisClient, redisModule);
- require('./redis/sets')(redisClient, redisModule);
- require('./redis/sorted')(redisClient, redisModule);
- require('./redis/list')(redisClient, redisModule);
-
- if (typeof callback === '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 cxn;
@@ -88,7 +92,11 @@ redisModule.connect = function (options) {
cxn.on('error', function (err) {
winston.error(err.stack);
- process.exit(1);
+ callback(err);
+ });
+
+ cxn.on('ready', function () {
+ callback();
});
if (nconf.get('redis:password')) {
@@ -99,7 +107,7 @@ redisModule.connect = function (options) {
if (dbIdx >= 0) {
cxn.select(dbIdx, function (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;
}
});
diff --git a/src/emailer.js b/src/emailer.js
index 10163cc512..a841eb1f91 100644
--- a/src/emailer.js
+++ b/src/emailer.js
@@ -10,6 +10,7 @@ var htmlToText = require('html-to-text');
var url = require('url');
var path = require('path');
var fs = require('fs');
+var _ = require('lodash');
var User = require('./user');
var Plugins = require('./plugins');
@@ -289,11 +290,10 @@ function buildCustomTemplates(config) {
file.walk(viewsDir, next);
},
function (paths, next) {
- paths = paths.reduce(function (obj, p) {
- var relative = path.relative(viewsDir, p);
- obj['/' + relative] = p;
- return obj;
- }, {});
+ paths = _.fromPairs(paths.map(function (p) {
+ var relative = path.relative(viewsDir, p).replace(/\\/g, '/');
+ return [relative, p];
+ }));
meta.templates.processImports(paths, template.path, template.text, next);
},
function (source, next) {
diff --git a/src/flags.js b/src/flags.js
index 4118cf8fb2..f906954318 100644
--- a/src/flags.js
+++ b/src/flags.js
@@ -241,7 +241,7 @@ Flags.validate = function (payload, callback) {
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)
if (!editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) {
return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
@@ -257,7 +257,7 @@ Flags.validate = function (payload, callback) {
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)
if (!editable && parseInt(data.reporter.reputation, 10) < minimumReputation) {
return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
diff --git a/src/install.js b/src/install.js
index 2906adc9e8..33bd5a58d1 100644
--- a/src/install.js
+++ b/src/install.js
@@ -157,16 +157,17 @@ function completeConfigSetup(config, next) {
}
}
+ nconf.overrides(config);
async.waterfall([
- function (next) {
- install.save(config, next);
- },
function (next) {
require('./database').init(next);
},
function (next) {
require('./database').createIndices(next);
},
+ function (next) {
+ install.save(config, next);
+ },
], next);
}
@@ -523,7 +524,7 @@ install.setup = function (callback) {
], function (err, results) {
if (err) {
winston.warn('NodeBB Setup Aborted.\n ' + err.stack);
- process.exit();
+ process.exit(1);
} else {
var data = {};
if (results[6]) {
diff --git a/src/meta/js.js b/src/meta/js.js
index ada16d56be..fb3d12f683 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -343,6 +343,11 @@ JS.buildBundle = function (target, fork, callback) {
function (next) {
getBundleScriptList(target, next);
},
+ function (files, next) {
+ mkdirp(path.join(__dirname, '../../build/public'), function (err) {
+ next(err, files);
+ });
+ },
function (files, next) {
var minify = global.env !== 'development';
var filePath = path.join(__dirname, '../../build/public', fileNames[target]);
diff --git a/src/meta/minifier.js b/src/meta/minifier.js
index ed6a68a625..e14761a707 100644
--- a/src/meta/minifier.js
+++ b/src/meta/minifier.js
@@ -75,7 +75,7 @@ function forkAction(action, callback) {
freeChild(proc);
if (message.type === 'error') {
- return callback(message.err);
+ return callback(message.message);
}
if (message.type === 'end') {
@@ -103,7 +103,7 @@ if (process.env.minifier_child) {
if (typeof actions[action.act] !== 'function') {
process.send({
type: 'error',
- err: Error('Unknown action'),
+ message: 'Unknown action',
});
return;
}
@@ -112,7 +112,7 @@ if (process.env.minifier_child) {
if (err) {
process.send({
type: 'error',
- err: err,
+ message: err.stack,
});
return;
}
diff --git a/src/meta/templates.js b/src/meta/templates.js
index 943978ff56..f8b63d41fa 100644
--- a/src/meta/templates.js
+++ b/src/meta/templates.js
@@ -7,6 +7,7 @@ var async = require('async');
var path = require('path');
var fs = require('fs');
var nconf = require('nconf');
+var _ = require('lodash');
var plugins = require('../plugins');
var file = require('../file');
@@ -24,7 +25,7 @@ function processImports(paths, templatePath, source, callback) {
return callback(null, source);
}
- var partial = '/' + matches[1];
+ var partial = matches[1];
if (paths[partial] && templatePath !== partial) {
fs.readFile(paths[partial], 'utf8', function (err, partialSource) {
if (err) {
@@ -43,124 +44,108 @@ function processImports(paths, templatePath, source, callback) {
}
Templates.processImports = processImports;
-Templates.compile = function (callback) {
- callback = callback || function () {};
+function getTemplateDirs(callback) {
+ 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 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([
+ function (cb) {
+ async.map(dirs, function (dir, next) {
+ file.walk(dir, function (err, files) {
+ if (err) { return next(err); }
+
+ files = files.filter(function (path) {
+ return path.endsWith('.tpl');
+ }).map(function (file) {
+ return {
+ name: path.relative(dir, file).replace(/\\/g, '/'),
+ path: file,
+ };
+ });
+ next(null, files);
+ });
+ }, cb);
+ },
+ function (buckets, cb) {
+ var dict = {};
+ buckets.forEach(function (files) {
+ files.forEach(function (file) {
+ dict[file.name] = file.path;
+ });
+ });
+
+ cb(null, dict);
+ },
+ ], callback);
+}
+
+function compile(callback) {
+ callback = callback || function () {};
async.waterfall([
function (next) {
- preparePaths(baseTemplatesPaths, next);
+ rimraf(viewsPath, function (err) { next(err); });
+ },
+ function (next) {
+ mkdirp(viewsPath, function (err) { next(err); });
},
- function (paths, next) {
- async.each(Object.keys(paths), function (relativePath, next) {
+ getTemplateDirs,
+ getTemplateFiles,
+ function (files, next) {
+ async.each(Object.keys(files), function (name, next) {
+ var filePath = files[name];
+
async.waterfall([
function (next) {
- fs.readFile(paths[relativePath], 'utf8', next);
+ fs.readFile(filePath, 'utf8', next);
},
function (source, next) {
- processImports(paths, relativePath, source, next);
+ processImports(files, name, source, next);
},
function (source, next) {
- mkdirp(path.join(viewsPath, path.dirname(relativePath)), function (err) {
+ mkdirp(path.join(viewsPath, path.dirname(name)), function (err) {
next(err, source);
});
},
function (compiled, next) {
- fs.writeFile(path.join(viewsPath, relativePath), compiled, next);
+ fs.writeFile(path.join(viewsPath, name), compiled, next);
},
], next);
}, next);
},
- function (next) {
- rimraf(path.join(viewsPath, '*.js'), next);
- },
function (next) {
winston.verbose('[meta/templates] Successfully compiled templates.');
next();
},
], 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 preparePaths(baseTemplatesPaths, callback) {
- var coreTemplatesPath = nconf.get('core_templates_path');
- var pluginTemplates;
- async.waterfall([
- function (next) {
- rimraf(viewsPath, next);
- },
- function (next) {
- mkdirp(viewsPath, next);
- },
- function (viewsPath, next) {
- plugins.fireHook('static:templates.precompile', {}, next);
- },
- function (next) {
- plugins.getTemplates(next);
- },
- function (_pluginTemplates, next) {
- pluginTemplates = _pluginTemplates;
- winston.verbose('[meta/templates] Compiling templates');
-
- async.parallel({
- coreTpls: function (next) {
- file.walk(coreTemplatesPath, next);
- },
- baseThemes: function (next) {
- async.map(baseTemplatesPaths, function (baseTemplatePath, next) {
- file.walk(baseTemplatePath, function (err, paths) {
- paths = paths.map(function (tpl) {
- return {
- base: baseTemplatePath,
- path: tpl.replace(baseTemplatePath, ''),
- };
- });
-
- next(err, paths);
- });
- }, next);
- },
- }, next);
- },
- function (data, next) {
- var baseThemes = data.baseThemes;
- var coreTpls = data.coreTpls;
- 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);
}
+Templates.compile = compile;
diff --git a/src/plugins.js b/src/plugins.js
index f11ed63494..a1193125e8 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -138,10 +138,13 @@ Plugins.reloadRoutes = function (callback) {
});
};
+// DEPRECATED: remove in v1.8.0
Plugins.getTemplates = function (callback) {
var templates = {};
var tplName;
+ winston.warn('[deprecated] Plugins.getTemplates is DEPRECATED to be removed in v1.8.0');
+
Plugins.data.getActive(function (err, plugins) {
if (err) {
return callback(err);
diff --git a/src/posts/edit.js b/src/posts/edit.js
index 0a9867feeb..991639af40 100644
--- a/src/posts/edit.js
+++ b/src/posts/edit.js
@@ -140,7 +140,7 @@ module.exports = function (Posts) {
db.setObject('topic:' + tid, results.topic, next);
},
function (next) {
- topics.updateTags(tid, data.tags, next);
+ topics.updateTopicTags(tid, data.tags, next);
},
function (next) {
topics.getTopicTagsObjects(tid, next);
diff --git a/src/posts/votes.js b/src/posts/votes.js
index 529fcbd0c3..422ca67efa 100644
--- a/src/posts/votes.js
+++ b/src/posts/votes.js
@@ -179,7 +179,7 @@ module.exports = function (Posts) {
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]]'));
}
diff --git a/src/privileges/posts.js b/src/privileges/posts.js
index b157fa798b..1741dfa587 100644
--- a/src/privileges/posts.js
+++ b/src/privileges/posts.js
@@ -200,7 +200,7 @@ module.exports = function (privileges) {
}, 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;
next(null, { flag: canFlag });
},
diff --git a/src/socket.io/admin/tags.js b/src/socket.io/admin/tags.js
index 8fe50790eb..f3c403e704 100644
--- a/src/socket.io/admin/tags.js
+++ b/src/socket.io/admin/tags.js
@@ -13,11 +13,19 @@ Tags.create = 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]]'));
}
- topics.updateTag(data.tag, data, callback);
+ topics.renameTags(data, callback);
};
Tags.deleteTags = function (socket, data, callback) {
diff --git a/src/topics/tags.js b/src/topics/tags.js
index 48cf353005..6d725c2269 100644
--- a/src/topics/tags.js
+++ b/src/topics/tags.js
@@ -9,7 +9,7 @@ var meta = require('../meta');
var _ = require('lodash');
var plugins = require('../plugins');
var utils = require('../utils');
-
+var batch = require('../batch');
module.exports = function (Topics) {
Topics.createTags = function (tags, tid, timestamp, callback) {
@@ -96,13 +96,61 @@ module.exports = function (Topics) {
], callback);
};
- Topics.updateTag = function (tag, data, callback) {
- if (!tag) {
- return setImmediate(callback, new Error('[[error:invalid-tag]]'));
- }
- db.setObject('tag:' + tag, data, callback);
+ Topics.updateTags = function (data, callback) {
+ async.eachSeries(data, function (tagData, next) {
+ db.setObject('tag:' + tagData.value, {
+ color: tagData.color,
+ 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) {
callback = callback || function () {};
async.waterfall([
@@ -148,7 +196,9 @@ module.exports = function (Topics) {
return 'tag:' + tag;
}), next);
},
- ], callback);
+ ], function (err) {
+ callback(err);
+ });
};
function removeTagsFromTopics(tags, callback) {
@@ -266,7 +316,7 @@ module.exports = function (Topics) {
], callback);
};
- Topics.updateTags = function (tid, tags, callback) {
+ Topics.updateTopicTags = function (tid, tags, callback) {
callback = callback || function () {};
async.waterfall([
function (next) {
diff --git a/src/upgrades/1.7.3/key_value_schema_change.js b/src/upgrades/1.7.3/key_value_schema_change.js
index 4e747f6846..637d2c534d 100644
--- a/src/upgrades/1.7.3/key_value_schema_change.js
+++ b/src/upgrades/1.7.3/key_value_schema_change.js
@@ -48,7 +48,7 @@ module.exports = {
done = true;
return next();
}
-
+ delete item.expireAt;
if (Object.keys(item).length === 3 && item.hasOwnProperty('_key') && item.hasOwnProperty('value')) {
client.collection('objects').update({ _key: item._key }, { $rename: { value: 'data' } }, next);
} else {
diff --git a/src/upgrades/1.8.0/rename_min_reputation_settings.js b/src/upgrades/1.8.0/rename_min_reputation_settings.js
new file mode 100644
index 0000000000..1abbce1378
--- /dev/null
+++ b/src/upgrades/1.8.0/rename_min_reputation_settings.js
@@ -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);
+ });
+ });
+ },
+};
diff --git a/src/user/profile.js b/src/user/profile.js
index ae1e31c475..1af4821f49 100644
--- a/src/user/profile.js
+++ b/src/user/profile.js
@@ -17,14 +17,6 @@ module.exports = function (User) {
var updateUid = data.uid;
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([
function (next) {
plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next);
@@ -33,13 +25,7 @@ module.exports = function (User) {
fields = data.fields;
data = data.data;
- async.series([
- async.apply(isEmailAvailable, data, updateUid),
- async.apply(isUsernameAvailable, data, updateUid),
- async.apply(isGroupTitleValid, data),
- ], function (err) {
- next(err);
- });
+ validateData(uid, data, next);
},
function (next) {
User.getUserFields(updateUid, fields, next);
@@ -73,6 +59,19 @@ module.exports = function (User) {
], 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) {
if (!data.email) {
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) {
async.waterfall([
function (next) {
diff --git a/src/views/admin/manage/tags.tpl b/src/views/admin/manage/tags.tpl
index 8fa01a1200..56c1c5b8c7 100644
--- a/src/views/admin/manage/tags.tpl
+++ b/src/views/admin/manage/tags.tpl
@@ -41,6 +41,7 @@
[[admin/manage/tags:description]]
+ @@ -74,4 +75,11 @@ + + diff --git a/src/views/admin/settings/reputation.tpl b/src/views/admin/settings/reputation.tpl index aee71910d4..7843273d2d 100644 --- a/src/views/admin/settings/reputation.tpl +++ b/src/views/admin/settings/reputation.tpl @@ -32,8 +32,11 @@