mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-05 12:01:17 +01:00
Merge branch 'master' into develop
This commit is contained in:
@@ -38,5 +38,5 @@
|
||||
"bookmarkThreshold": 5,
|
||||
"topicsPerList": 20,
|
||||
"autoDetectLang": 1,
|
||||
"privileges:flag": 0
|
||||
"min:rep:flag": 0
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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\")"
|
||||
}
|
||||
@@ -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?",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
@@ -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)",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"topics": "Themen",
|
||||
"posts": "Beiträge",
|
||||
"best": "Bestbewertet",
|
||||
"votes": "Votes",
|
||||
"votes": "Stimmen",
|
||||
"upvoters": "Upvoter",
|
||||
"upvoted": "Positiv bewertet",
|
||||
"downvoters": "Downvoter",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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 <a href=\"%1\">here</a> to visit the tag settings page.",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Негативно гласање је онемогућено",
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"topics": "Теме",
|
||||
"posts": "Поруке",
|
||||
"best": "Најбоље",
|
||||
"votes": "Votes",
|
||||
"votes": "Гласови",
|
||||
"upvoters": "Позитивно гласали",
|
||||
"upvoted": "Позитивно гласано",
|
||||
"downvoters": "Негативно гласали",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"popular-month": "Популарне теме овог месеца",
|
||||
"popular-alltime": "Популарне теме свих времена",
|
||||
"recent": "Недавне теме",
|
||||
"top": "Top Voted Topics",
|
||||
"top": "Најгласаније теме",
|
||||
"moderator-tools": "Алати модератора",
|
||||
"flagged-content": "Садржај означен заставицом",
|
||||
"ip-blacklist": "Црна листа IP адреса",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"not-watching.description": "Немој ме обавештавати о новим одговорима.<br/>Прикажи тему у непрочитаним ако категорија није игнорисана.",
|
||||
"ignoring.description": "Немој ме обавештавати о новим одговорима.<br/>Не приказуј тему у непрочитаним",
|
||||
"thread_tools.title": "Алатке теме",
|
||||
"thread_tools.markAsUnreadForAll": "Mark Unread For All",
|
||||
"thread_tools.markAsUnreadForAll": "Означи као непрочитано за све",
|
||||
"thread_tools.pin": "Закачи тему",
|
||||
"thread_tools.unpin": "Откачи тему",
|
||||
"thread_tools.lock": "Закључај тему",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"topics": "Başlık",
|
||||
"posts": "İleti",
|
||||
"best": "En İyi",
|
||||
"votes": "Votes",
|
||||
"votes": "Oy",
|
||||
"upvoters": "Artı",
|
||||
"upvoted": "Artı",
|
||||
"downvoters": "Eksi",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ı",
|
||||
|
||||
@@ -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”)"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" */
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
title: $(this).attr('title'),
|
||||
});
|
||||
});
|
||||
utils.addNoReferrer(widgetAreas);
|
||||
$(window).trigger('action:widgets.loaded', {});
|
||||
callback();
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]]' },
|
||||
|
||||
@@ -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;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]]'));
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 (paths, next) {
|
||||
async.each(Object.keys(paths), function (relativePath, next) {
|
||||
function (next) {
|
||||
mkdirp(viewsPath, function (err) { next(err); });
|
||||
},
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]]'));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
|
||||
@@ -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.updateTag(data.tag, data, callback);
|
||||
topics.updateTags(data, callback);
|
||||
};
|
||||
|
||||
Tags.rename = function (socket, data, callback) {
|
||||
if (!Array.isArray(data)) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
topics.renameTags(data, callback);
|
||||
};
|
||||
|
||||
Tags.deleteTags = function (socket, data, callback) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
25
src/upgrades/1.8.0/rename_min_reputation_settings.js
Normal file
25
src/upgrades/1.8.0/rename_min_reputation_settings.js
Normal file
@@ -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 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) {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,4 +75,11 @@
|
||||
</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>
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/reputation:thresholds]]</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<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-flag]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="privileges:flag"><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="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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -591,21 +591,21 @@ describe('Admin Controllers', function () {
|
||||
|
||||
it('should error with not enough reputation to flag', function (done) {
|
||||
var socketFlags = require('../src/socket.io/flags');
|
||||
var oldValue = meta.config['privileges:flag'];
|
||||
meta.config['privileges:flag'] = 1000;
|
||||
var oldValue = meta.config['min:rep:flag'];
|
||||
meta.config['min:rep:flag'] = 1000;
|
||||
socketFlags.create({ uid: regularUid }, { id: pid, type: 'post', reason: 'spam' }, function (err) {
|
||||
assert.equal(err.message, '[[error:not-enough-reputation-to-flag]]');
|
||||
meta.config['privileges:flag'] = oldValue;
|
||||
meta.config['min:rep:flag'] = oldValue;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return flag details', function (done) {
|
||||
var socketFlags = require('../src/socket.io/flags');
|
||||
var oldValue = meta.config['privileges:flag'];
|
||||
meta.config['privileges:flag'] = 0;
|
||||
var oldValue = meta.config['min:rep:flag'];
|
||||
meta.config['min:rep:flag'] = 0;
|
||||
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);
|
||||
request(nconf.get('url') + '/api/flags/' + data.flagId, { jar: moderatorJar, json: true }, function (err, res, body) {
|
||||
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) {
|
||||
Meta.configs.set('privileges:flag', '50', function (err) {
|
||||
Meta.configs.set('min:rep:flag', '50', function (err) {
|
||||
assert.ifError(err);
|
||||
|
||||
Flags.validate({
|
||||
@@ -374,7 +374,7 @@ describe('Flags', function () {
|
||||
}, function (err) {
|
||||
assert.ok(err);
|
||||
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 }, {
|
||||
bgColor: '#ff0000',
|
||||
color: '#00ff00',
|
||||
}, function (err) {
|
||||
assert.equal(err.message, '[[error:invalid-tag]]');
|
||||
assert.equal(err.message, '[[error:invalid-data]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update tag', function (done) {
|
||||
socketAdmin.tags.update({ uid: adminUid }, {
|
||||
tag: 'emptytag',
|
||||
socketAdmin.tags.update({ uid: adminUid }, [{
|
||||
value: 'emptytag',
|
||||
bgColor: '#ff0000',
|
||||
color: '#00ff00',
|
||||
}, function (err) {
|
||||
}], function (err) {
|
||||
assert.ifError(err);
|
||||
db.getObject('tag:emptytag', function (err, data) {
|
||||
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) {
|
||||
var meta = require('../src/meta');
|
||||
meta.config.maximumRelatedTopics = 2;
|
||||
|
||||
Reference in New Issue
Block a user