diff --git a/install/package.json b/install/package.json
index c51992816c..e8fa9735fc 100644
--- a/install/package.json
+++ b/install/package.json
@@ -59,7 +59,7 @@
"morgan": "^1.9.0",
"mousetrap": "^1.6.1",
"nconf": "^0.9.1",
- "nodebb-plugin-composer-default": "6.0.7",
+ "nodebb-plugin-composer-default": "6.0.8",
"nodebb-plugin-dbsearch": "2.0.9",
"nodebb-plugin-emoji": "2.0.9",
"nodebb-plugin-emoji-android": "2.0.0",
@@ -69,7 +69,7 @@
"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.8",
+ "nodebb-theme-persona": "7.2.9",
"nodebb-theme-slick": "1.1.2",
"nodebb-theme-vanilla": "8.1.4",
"nodebb-widget-essentials": "4.0.1",
diff --git a/public/language/cs/admin/appearance/customise.json b/public/language/cs/admin/appearance/customise.json
index c22a869f80..0cd4e0f8da 100644
--- a/public/language/cs/admin/appearance/customise.json
+++ b/public/language/cs/admin/appearance/customise.json
@@ -3,12 +3,12 @@
"custom-css.description": "Zadejte vlastní deklarace CSS, které budou použity na všechny ostatních styly.",
"custom-css.enable": "Povolit uživatelské CSS",
- "custom-js": "Custom Javascript",
- "custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.",
- "custom-js.enable": "Enable Custom Javascript",
+ "custom-js": "Uživatelský Javascript",
+ "custom-js.description": "Zadejte zde váš javascriptový kód. Bude spuštěn, jakmile se stránka plně načte.",
+ "custom-js.enable": "Povolit uživatelský Javascript",
"custom-header": "Uživatelská hlavička",
- "custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <head>
section of your forum's markup. Script tags are allowed, but are discouraged, as the Custom Javascript tab is available.",
+ "custom-header.description": "Zde zadejte uživatelské HTML (mimo Meta Tags, atd.), které bude připojeno k části značek <head>
vašeho fóra.. Značky pro „script” jsou povoleny, ale nedoporučujeme je, neboť je dostupný Uživatelský Javascript .",
"custom-header.enable": "Povolit uživatelskou hlavičku",
"custom-css.livereload": "Povolit aktuální znovu načtení",
diff --git a/public/language/cs/admin/manage/post-queue.json b/public/language/cs/admin/manage/post-queue.json
index 57fe1f8ab7..e46490b1b7 100644
--- a/public/language/cs/admin/manage/post-queue.json
+++ b/public/language/cs/admin/manage/post-queue.json
@@ -7,5 +7,5 @@
"content": "Obsah",
"posted": "Přidáno",
"reply-to": "Odpovědět na \"%1\"",
- "content-editable": "You can click on individual content to edit before posting."
+ "content-editable": "Kvůli úpravám a před odesláním příspěvku můžete klikat na obsah."
}
\ No newline at end of file
diff --git a/public/language/cs/admin/menu.json b/public/language/cs/admin/menu.json
index 8b9812e514..7de94b7a5c 100644
--- a/public/language/cs/admin/menu.json
+++ b/public/language/cs/admin/menu.json
@@ -39,7 +39,7 @@
"section-appearance": "Vzhled",
"appearance/themes": "Motivy",
"appearance/skins": "Vzhledy",
- "appearance/customise": "Custom Content (HTML/JS/CSS)",
+ "appearance/customise": "Uživatelský obsah (HTML/JS/CSS)",
"section-extend": "Rozšířit",
"extend/plugins": "Rozšíření",
@@ -65,7 +65,7 @@
"logout": "Odhlásit",
"view-forum": "Zobrazit fórum",
- "search.placeholder": "Search for settings",
+ "search.placeholder": "Hledat nastavení",
"search.no-results": "Žádné výsledky…",
"search.search-forum": "Prohledat fórum pro ",
"search.keep-typing": "Pište dále pro zobrazení výsledků…",
diff --git a/public/language/cs/admin/settings/notifications.json b/public/language/cs/admin/settings/notifications.json
index fd95917606..39bc83bdcb 100644
--- a/public/language/cs/admin/settings/notifications.json
+++ b/public/language/cs/admin/settings/notifications.json
@@ -2,5 +2,5 @@
"notifications": "Oznámení",
"welcome-notification": "Uvítání",
"welcome-notification-link": "Odkaz na uvítání",
- "welcome-notification-uid": "Welcome Notification User (UID)"
+ "welcome-notification-uid": "Uvítání uživatele (UID)"
}
\ No newline at end of file
diff --git a/public/language/cs/admin/settings/pagination.json b/public/language/cs/admin/settings/pagination.json
index dc1faf68da..34052b2a9c 100644
--- a/public/language/cs/admin/settings/pagination.json
+++ b/public/language/cs/admin/settings/pagination.json
@@ -3,9 +3,9 @@
"enable": "Stránkovat témata a příspěvky namísto nekonečného posouvání",
"topics": "Stránkování témat",
"posts-per-page": "Příspěvků na stránku",
- "max-posts-per-page": "Maximum posts per page",
+ "max-posts-per-page": "Maximální množství příspěvků na stránku",
"categories": "Stránkování kategorii",
"topics-per-page": "Témat na stránku",
- "max-topics-per-page": "Maximum topics per page",
+ "max-topics-per-page": "Maximální množství témat na stránku",
"initial-num-load": "Počáteční počet témat pro načtení u nepřečtených, posledních a polulárních"
}
\ No newline at end of file
diff --git a/public/language/cs/admin/settings/post.json b/public/language/cs/admin/settings/post.json
index 67d297b33b..de8832b501 100644
--- a/public/language/cs/admin/settings/post.json
+++ b/public/language/cs/admin/settings/post.json
@@ -3,8 +3,8 @@
"sorting.post-default": "Výchozí třídění příspěvků",
"sorting.oldest-to-newest": "Od nejstarších po nejnovější",
"sorting.newest-to-oldest": "Od nejnovějších po nejstarší",
- "sorting.most-votes": "Dle hlasování",
- "sorting.most-posts": "Most Posts",
+ "sorting.most-votes": "Dle počtu hlasů",
+ "sorting.most-posts": "Dle počtu příspěvků",
"sorting.topic-default": "Výchozí třídění tématu",
"restrictions": "Omezení příspěvků",
"restrictions.post-queue": "Povolit frontu pro příspěvky",
diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json
index 2ad41f23cf..e683492860 100644
--- a/public/language/cs/admin/settings/user.json
+++ b/public/language/cs/admin/settings/user.json
@@ -19,8 +19,8 @@
"themes": "Motivy",
"disable-user-skins": "Zabránit uživateli ve výběru vlastního vzhledu",
"account-protection": "Ochrana účtu",
- "admin-relogin-duration": "Admin relogin duration (minutes)",
- "admin-relogin-duration-help": "After a set amount of time accessing the admin section will require re-login, set to 0 to disable",
+ "admin-relogin-duration": "Doba pro opětovné přihlášení správce (minuty)",
+ "admin-relogin-duration-help": "Po nastavení počtu přístupu do správcovské části, bude vyžadováno opětovné přihlášení. Pro zakázání, nastavte na 0.",
"login-attempts": "Počet pokusů o přihlášení za hodinu",
"login-attempts-help": "Překročí-li pokusy o přihlášení uživatele/ů tuto hranici, účet bude uzamknut na určený čas",
"lockout-duration": "Délka blokování účtu (v minutách)",
diff --git a/public/language/cs/email.json b/public/language/cs/email.json
index 1d1efa10e9..b330291160 100644
--- a/public/language/cs/email.json
+++ b/public/language/cs/email.json
@@ -30,7 +30,7 @@
"notif.chat.unsub.info": "Toto upozornění na chat vám bylo odesláno na základě vašeho nastavení odběru.",
"notif.post.cta": "Klikněte zde pro přečtené celého tématu",
"notif.post.unsub.info": "Toto upozornění na příspěvek vám bylo odesláno na základě vašeho nastavení odběru.",
- "notif.cta": "Click here to go to forum",
+ "notif.cta": "Pro přejití na fórum, klikněte zde",
"test.text1": "Tento testovací e-mail slouží k ověření, že je e-mailer správně nastaven pro práci s NodeBB.",
"unsub.cta": "Chcete-li změnit tyto nastavení, klikněte zde.",
"banned.subject": "Byl jste zablokován od %1",
diff --git a/public/language/cs/error.json b/public/language/cs/error.json
index 43ea641999..18576ee9bc 100644
--- a/public/language/cs/error.json
+++ b/public/language/cs/error.json
@@ -11,7 +11,7 @@
"invalid-uid": "Neplatné ID uživatele",
"invalid-username": "Neplatné uživatelské jméno",
"invalid-email": "Neplatný e-mail",
- "invalid-title": "Invalid title",
+ "invalid-title": "Neplatný název",
"invalid-user-data": "Neplatná uživatelská data",
"invalid-password": "Neplatné heslo",
"invalid-login-credentials": "Neplatné přihlašovací údaje",
@@ -81,7 +81,7 @@
"cant-ban-other-admins": "Nemůžete zablokovat jiné správce.",
"cant-remove-last-admin": "Jste jediným správcem. Před vlastním odebráním oprávnění správce nejdříve přidejte jiného uživatele jako správce",
"cant-delete-admin": "Před odstraněním účtu mu nejprve odeberte oprávnění správce.",
- "invalid-image": "Invalid image",
+ "invalid-image": "Neplatný obrázek",
"invalid-image-type": "Neplatný typ obrázku. Povolené typy jsou: %1",
"invalid-image-extension": "Neplatná přípona obrázku",
"invalid-file-type": "Neplatný typ souboru. Povolené typy jsou: %1",
@@ -119,13 +119,13 @@
"not-enough-reputation-to-downvote": "Nemáte dostatečnou reputaci pro vyjádření nesouhlasu u tohoto příspěvku",
"not-enough-reputation-to-flag": "Pro označení tohoto příspěvku nemáte dostatečnou reputaci",
"already-flagged": "Tento příspěvek jste již označil",
- "self-vote": "You cannot vote on your own post",
+ "self-vote": "U svého vlastního příspěvku nemůžete hlasovat",
"reload-failed": "Vyskytla se chyba v NodeBB při znovu načtení: \"%1\". NodeBB bude pokračovat v běhu na straně klienta, nicméně byste měl/a přenastavit zpět to, co jste udělal/a před opětovným načtením.",
"registration-error": "Chyba při registraci",
"parse-error": "Při analýze odpovědi serveru nastala chyba",
"wrong-login-type-email": "Pro přihlášení použijte vaši e-mailovou adresu",
"wrong-login-type-username": "Pro přihlášení použijte vaše uživatelské jméno",
- "sso-registration-disabled": "Registration has been disabled for %1 accounts, please register with an email address first",
+ "sso-registration-disabled": "Registrace byla zakázána pro účty - %1. Nejprve si zaregistrujte e-mailovou adresu",
"invite-maximum-met": "Již jste pozval/a maximálně možný počet lidí (%1 z %2).",
"no-session-found": "Nebyla nalezena relace s přihlášením.",
"not-in-room": "Uživatel není přítomen v místnosti",
@@ -135,5 +135,5 @@
"invalid-home-page-route": "Neplatná cesta k domovské stránkce",
"invalid-session": "Nesoulad v relacích",
"invalid-session-text": "Zdá se, že vše relace s přihlášením již není aktivní nebo již neodpovídá s relací na serveru. Obnovte prosím tuto stránku.",
- "no-topics-selected": "No topics selected!"
+ "no-topics-selected": "Žádná vybraná témata."
}
\ No newline at end of file
diff --git a/public/language/cs/flags.json b/public/language/cs/flags.json
index e221f6965b..eb37572f8a 100644
--- a/public/language/cs/flags.json
+++ b/public/language/cs/flags.json
@@ -54,11 +54,11 @@
"modal-body": "Zadejte váš důvod k označení %1 %2 pro kontrolu. Nebo použijte tlačítko je-li dostupné.",
"modal-reason-spam": "Spam",
"modal-reason-offensive": "Urážlivé",
- "modal-reason-other": "Other (specify below)",
+ "modal-reason-other": "Jiné (popište níže)",
"modal-reason-custom": "Důvod ohlášení tohoto obsahu…",
"modal-submit": "Předat hlášení",
"modal-submit-success": "Obsah byl označen pro moderaci.",
- "modal-submit-confirm": "Confirm Submission",
- "modal-submit-confirm-text": "You have a custom reason specified already. Are you sure you wish to submit via quick-report?",
- "modal-submit-confirm-text-help": "Submitting a quick report will overwrite any custom reasons defined."
+ "modal-submit-confirm": "Potvrdit hlášení",
+ "modal-submit-confirm-text": "Již jste zadal/a nějaký důvod. Jste si jist/a, že chcete nahlásit pomocí rychlé zprávy?",
+ "modal-submit-confirm-text-help": "Zaslání rychlé zprávy přepíše jiné zadané důvody."
}
\ No newline at end of file
diff --git a/public/language/cs/notifications.json b/public/language/cs/notifications.json
index 8182ae3f47..284b394c6f 100644
--- a/public/language/cs/notifications.json
+++ b/public/language/cs/notifications.json
@@ -8,8 +8,8 @@
"outgoing_link_message": "Opouštíte %1",
"continue_to": "Pokračovat na %1",
"return_to": "Vrátit se na %1",
- "new_notification": "Nové upozornění",
- "new_notification_from": "You have a new Notification from %1",
+ "new_notification": "Nové oznámení",
+ "new_notification_from": "Máte nové upozornění od %1",
"you_have_unread_notifications": "Máte nepřečtená upozornění.",
"all": "Vše",
"topics": "Témata",
@@ -47,18 +47,18 @@
"email-confirmed-message": "Děkujeme za ověření vaší e-mailové adresy. Váš účet je nyní aktivní.",
"email-confirm-error-message": "Nastal problém s ověřením vaší e-mailové adresy. Kód je pravděpodobně neplatný nebo jeho platnost vypršela.",
"email-confirm-sent": "Ověřovací e-mail odeslán.",
- "none": "None",
- "notification_only": "Notification Only",
- "email_only": "Email Only",
- "notification_and_email": "Notification & Email",
- "notificationType_upvote": "When someone upvotes your post",
- "notificationType_new-topic": "When someone you follow posts a topic",
- "notificationType_new-reply": "When a new reply is posted in a topic you are watching",
- "notificationType_follow": "When someone starts following you",
- "notificationType_new-chat": "When you receive a chat message",
- "notificationType_group-invite": "When you receive a group invite",
- "notificationType_new-register": "When someone gets added to registration queue",
- "notificationType_post-queue": "When a new post is queued",
- "notificationType_new-post-flag": "When a post is flagged",
- "notificationType_new-user-flag": "When a user is flagged"
+ "none": "Nic",
+ "notification_only": "Jen oznámení",
+ "email_only": "Jen e-mail",
+ "notification_and_email": "Oznámení a e-mail",
+ "notificationType_upvote": "Vyjádří-li někdo souhlas s vaším příspěvkem",
+ "notificationType_new-topic": "Začne-li někdo sledovat příspěvky a téma",
+ "notificationType_new-reply": "Bude-li přidán nový příspěvek v tématu, které sledujete",
+ "notificationType_follow": "Začne-li vás někdo sledovat",
+ "notificationType_new-chat": "Obdržíte-li novou konverzační zprávu",
+ "notificationType_group-invite": "Obdržíte-li pozvání ke skupině",
+ "notificationType_new-register": "Bude-li někdo přidán do registrační fronty",
+ "notificationType_post-queue": "Bude-li přidán nový příspěvek do fronty",
+ "notificationType_new-post-flag": "Bude-li příspěvek označen",
+ "notificationType_new-user-flag": "Bude-li uživatel označen"
}
\ No newline at end of file
diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json
index 8a0270a9db..befc7ceba3 100644
--- a/public/language/cs/pages.json
+++ b/public/language/cs/pages.json
@@ -44,7 +44,7 @@
"account/bookmarks": "%1's zazáložkované příspěvky",
"account/settings": "Uživatelské nastavení",
"account/watched": "Témata sledovaná uživatelem %1",
- "account/ignored": "Topics ignored by %1",
+ "account/ignored": "Témata ignorovaná uživatelem %1",
"account/upvoted": "Souhlasí s příspěvkem %1",
"account/downvoted": "Nesouhlasí s příspěvkem %1",
"account/best": "Nejlepší příspěvky od %1",
diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json
index 7cedca5e24..80e7561e26 100644
--- a/public/language/cs/topic.json
+++ b/public/language/cs/topic.json
@@ -68,8 +68,8 @@
"thread_tools.restore_confirm": "Jste si jist/a, že chcete toto téma obnovit?",
"thread_tools.purge": "Vyčistit téma",
"thread_tools.purge_confirm": "Jste si jist/a, že chcete vyčistit toto téma?",
- "thread_tools.merge_topics": "Merge Topics",
- "thread_tools.merge": "Merge",
+ "thread_tools.merge_topics": "Sloučit témata",
+ "thread_tools.merge": "Sloučit",
"topic_move_success": "Toto téma bylo úspěšně přesunuto do %1",
"post_delete_confirm": "Jste si jist/a, že chcete odstranit tento příspěvek?",
"post_restore_confirm": "Jste si jist/a, že chcete obnovit tento příspěvek?",
@@ -91,7 +91,7 @@
"fork_pid_count": "Vybráno %1 příspěvek/ů",
"fork_success": "Téma úspěšně rozděleno. Pro přejití na rozdělené téma, zde klikněte.",
"delete_posts_instruction": "Klikněte na příspěvek, který chcete odstranit/vyčistit",
- "merge_topics_instruction": "Click the topics you want to merge",
+ "merge_topics_instruction": "Pro sloučení témat, klikněte na ně",
"composer.title_placeholder": "Zadejte název tématu…",
"composer.handle_placeholder": "Jméno",
"composer.discard": "Zrušit",
diff --git a/public/language/cs/unread.json b/public/language/cs/unread.json
index 60381d5efd..35035c5cb0 100644
--- a/public/language/cs/unread.json
+++ b/public/language/cs/unread.json
@@ -10,6 +10,6 @@
"all-topics": "Všechna témata",
"new-topics": "Nová témata",
"watched-topics": "Sledovaná témata",
- "unreplied-topics": "Unreplied Topics",
- "multiple-categories-selected": "Multiple Selected"
+ "unreplied-topics": "Neodpovězené témata",
+ "multiple-categories-selected": "Vícenásobný výběr"
}
\ No newline at end of file
diff --git a/public/language/cs/user.json b/public/language/cs/user.json
index 0042eb8ec6..8fce80c61c 100644
--- a/public/language/cs/user.json
+++ b/public/language/cs/user.json
@@ -25,7 +25,7 @@
"reputation": "Reputace",
"bookmarks": "Záložky",
"watched": "Sledován",
- "ignored": "Ignored",
+ "ignored": "Ignorován",
"followers": "Sledují ho",
"following": "Sleduje",
"aboutme": "O mně",
@@ -85,7 +85,7 @@
"has_no_posts": "Tento uživatel ještě nic nenapsal.",
"has_no_topics": "Tento uživatel ještě nezaložil žádné téma.",
"has_no_watched_topics": "Tento uživatel zatím nesleduje žádná témata.",
- "has_no_ignored_topics": "This user hasn't ignored any topics yet.",
+ "has_no_ignored_topics": "Tento uživatel ještě neignoruje žádné témata.",
"has_no_upvoted_posts": "Tento uživatel zatím nevyjádřil souhlas u žádného příspěvku.",
"has_no_downvoted_posts": "Tento uživatel zatím nevyjádřil nesouhlas u žádného příspěvku.",
"has_no_voted_posts": "Tento uživatel nemá žádné hlasovací příspěvky",
@@ -101,11 +101,11 @@
"outgoing-message-sound": "Zvuk odchozí zprávy",
"notification-sound": "Zvuk oznámení",
"no-sound": "Bez zvuku",
- "upvote-notif-freq": "Upvote Notification Frequency",
- "upvote-notif-freq.all": "All Upvotes",
- "upvote-notif-freq.everyTen": "Every Ten Upvotes",
- "upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
- "upvote-notif-freq.disabled": "Disabled",
+ "upvote-notif-freq": "Frekvence upozornění na souhlasy",
+ "upvote-notif-freq.all": "Všechny souhlasy",
+ "upvote-notif-freq.everyTen": "Každý desátý souhlas",
+ "upvote-notif-freq.logarithmic": "Dle 10, 100, 1000...",
+ "upvote-notif-freq.disabled": "Zakázáno",
"browsing": "Nastavení prohlížení",
"open_links_in_new_tab": "Otevřít odchozí odkaz v nové záložce",
"enable_topic_searching": "Povolit vyhledávání v tématu",
@@ -126,9 +126,9 @@
"sso.title": "Služby jednotného přihlášení",
"sso.associated": "Přiřazeno k",
"sso.not-associated": "Zde klikněte pro přiřazení k",
- "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": "Odloučit",
+ "sso.dissociate-confirm-title": "Potvrdit odloučení",
+ "sso.dissociate-confirm": "Jste si jist/a, že chcete odloučit váš účet z %1?",
"info.latest-flags": "Poslední označené",
"info.no-flags": "Nebyly nalezeny žádné označené příspěvky",
"info.ban-history": "Poslední historie blokovaných",
diff --git a/public/language/en-GB/admin/manage/admins-mods.json b/public/language/en-GB/admin/manage/admins-mods.json
new file mode 100644
index 0000000000..e0f39ed5d4
--- /dev/null
+++ b/public/language/en-GB/admin/manage/admins-mods.json
@@ -0,0 +1,10 @@
+{
+ "administrators": "Administrators",
+ "global-moderators": "Global Moderators",
+ "no-global-moderators": "No Global Moderators",
+ "moderators-of-category": "%1 Moderators",
+ "no-moderators": "No Moderators",
+ "add-administrator": "Add Administrator",
+ "add-global-moderator": "Add Global Moderator",
+ "add-moderator": "Add Moderator"
+}
\ No newline at end of file
diff --git a/public/language/en-GB/admin/manage/privileges.json b/public/language/en-GB/admin/manage/privileges.json
new file mode 100644
index 0000000000..b5b4f35885
--- /dev/null
+++ b/public/language/en-GB/admin/manage/privileges.json
@@ -0,0 +1,4 @@
+{
+ "global": "Global",
+ "global.no-users": "No user-specific global privileges."
+}
\ No newline at end of file
diff --git a/public/language/en-GB/admin/manage/users.json b/public/language/en-GB/admin/manage/users.json
index 5b68fcdc91..9dcc1a0f32 100644
--- a/public/language/en-GB/admin/manage/users.json
+++ b/public/language/en-GB/admin/manage/users.json
@@ -71,9 +71,15 @@
"alerts.lockout-reset-success": "Lockout(s) reset!",
"alerts.flag-reset-success": "Flags(s) reset!",
"alerts.no-remove-yourself-admin": "You can't remove yourself as Administrator!",
- "alerts.make-admin-success": "User(s) are now administrators.",
- "alerts.confirm-remove-admin": "Do you really want to remove admins?",
- "alerts.remove-admin-success": "User(s) are no longer administrators.",
+ "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.confirm-validate-email": "Do you want to validate email(s) of these user(s)?",
"alerts.validate-email-success": "Emails validated",
"alerts.password-reset-confirm": "Do you want to send password reset email(s) to these user(s)?",
diff --git a/public/language/en-GB/admin/menu.json b/public/language/en-GB/admin/menu.json
index 2b836ed0f7..8f44bcd304 100644
--- a/public/language/en-GB/admin/menu.json
+++ b/public/language/en-GB/admin/menu.json
@@ -9,8 +9,10 @@
"section-manage": "Manage",
"manage/categories": "Categories",
+ "manage/privileges": "Privileges",
"manage/tags": "Tags",
"manage/users": "Users",
+ "manage/admins-mods": "Admins & Mods",
"manage/registration": "Registration Queue",
"manage/post-queue": "Post Queue",
"manage/groups": "Groups",
diff --git a/public/language/fr/admin/appearance/customise.json b/public/language/fr/admin/appearance/customise.json
index ada04f6546..187f799efb 100644
--- a/public/language/fr/admin/appearance/customise.json
+++ b/public/language/fr/admin/appearance/customise.json
@@ -3,9 +3,9 @@
"custom-css.description": "Entrez vos propres déclarations de CSS ici, elles seront appliquées après tous les autres styles.",
"custom-css.enable": "Activer les CSS personnalisés",
- "custom-js": "Custom Javascript",
- "custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.",
- "custom-js.enable": "Enable Custom Javascript",
+ "custom-js": "Javascript personnalisé",
+ "custom-js.description": "Entrez votre Javascript ici. Celui-ci sera exécute après le chargement complet de la page.",
+ "custom-js.enable": "Activer le Javascript personnalisé",
"custom-header": "En-tête personnalisé",
"custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <head>
section of your forum's markup. Script tags are allowed, but are discouraged, as the Custom Javascript tab is available.",
diff --git a/public/language/it/email.json b/public/language/it/email.json
index bcada51df4..f6fdaef532 100644
--- a/public/language/it/email.json
+++ b/public/language/it/email.json
@@ -13,7 +13,7 @@
"reset.text1": "Abbiamo ricevuto una richiesta di reset della tua password, probabilmente perché l'hai dimenticata. Se non è così si prega di ignorare questa email.",
"reset.text2": "Per confermare il reset della password per favore clicca il seguente link:",
"reset.cta": "Clicca qui per resettare la tua password",
- "reset.notify.subject": "Possword modificata con successo.",
+ "reset.notify.subject": "Password modificata con successo.",
"reset.notify.text1": "Ti informiamo che in data %1, la password è stata cambiata con successo.",
"reset.notify.text2": "Se non hai autorizzato questo, per favore notifica immediatamente un amministratore.",
"digest.notifications": "Hai una notifica non letta da %1:",
@@ -33,9 +33,9 @@
"notif.cta": "Vai alla discussione",
"test.text1": "Questa è una email di test per verificare che il servizio di invio email è configurato correttamente sul tuo NodeBB.",
"unsub.cta": "Clicca qui per modificare queste impostazioni",
- "banned.subject": "You have been banned from %1",
- "banned.text1": "The user %1 has been banned from %2.",
- "banned.text2": "This ban will last until %1.",
- "banned.text3": "This is the reason why you have been banned:",
+ "banned.subject": "Sei stato bannato da %1",
+ "banned.text1": "%1 è stato bannato da %2",
+ "banned.text2": "Questo ban durerà fino a %1.",
+ "banned.text3": "Il motivo del ban è:",
"closing": "Grazie!"
}
\ No newline at end of file
diff --git a/public/language/it/error.json b/public/language/it/error.json
index 9fa55ff42e..0ad1f963ca 100644
--- a/public/language/it/error.json
+++ b/public/language/it/error.json
@@ -11,10 +11,10 @@
"invalid-uid": "ID Utente non valido",
"invalid-username": "Nome utente non valido",
"invalid-email": "Email non valida",
- "invalid-title": "Invalid title",
+ "invalid-title": "Titolo non Valido",
"invalid-user-data": "Dati Utente non validi",
"invalid-password": "Password non valida",
- "invalid-login-credentials": "Invalid login credentials",
+ "invalid-login-credentials": "Credenziali non Valide",
"invalid-username-or-password": "Si prega di specificare sia un nome utente che una password",
"invalid-search-term": "Termine di ricerca non valido",
"csrf-invalid": "Non siamo riusciti a farti connettere, probabilmente perché la sessione è scaduta. Per favore riprova.",
@@ -66,7 +66,7 @@
"content-too-long": "Inserisci un post più breve. I post non possono essere più lunghi di %1 caratteri.",
"title-too-short": "Inserisci un titolo più lungo. I titoli devono contenere almeno %1 caratteri.",
"title-too-long": "Inserisci un titolo più corto. I titoli non possono essere più lunghi di %1 caratteri.",
- "category-not-selected": "Category not selected.",
+ "category-not-selected": "Categoria non selezionata.",
"too-many-posts": "È possibile inserire un Post ogni %1 secondi - si prega di attendere prima di postare di nuovo",
"too-many-posts-newbie": "Come nuovo utente puoi postare solamente una volta ogni %1 secondi finché non hai raggiunto un livello di reputazione %2 - per favore attendi prima di scrivere ancora",
"tag-too-short": "Inserisci un tag più lungo. I tag devono contenere almeno %1 caratteri.",
@@ -76,12 +76,12 @@
"still-uploading": "Per favore attendere il completamento degli uploads.",
"file-too-big": "La dimensione massima consentita è di %1 kB - si prega di caricare un file più piccolo",
"guest-upload-disabled": "Il caricamento da ospite è stato disattivato",
- "already-bookmarked": "You have already bookmarked this post",
- "already-unbookmarked": "You have already unbookmarked this post",
+ "already-bookmarked": "Hai già aggiunto questa discussione ai preferiti.",
+ "already-unbookmarked": "Hai già rimosso questa discussione dai preferiti",
"cant-ban-other-admins": "Non puoi bannare altri amministratori!",
"cant-remove-last-admin": "Sei l'unico Amministratore. Aggiungi un altro amministratore prima di rimuovere il tuo ruolo",
"cant-delete-admin": "Togli i privilegi amministrativi da questo account prima di provare ad eliminarlo.",
- "invalid-image": "Invalid image",
+ "invalid-image": "Immagine non Valida",
"invalid-image-type": "Tipo dell'immagine non valido. I tipi permessi sono: %1",
"invalid-image-extension": "Estensione immagine non valida",
"invalid-file-type": "Tipo di file non valido. I formati consentiti sono: %1",
@@ -109,7 +109,7 @@
"chat-disabled": "Il sistema di chat è stato disabilitato",
"too-many-messages": "Hai inviato troppi messaggi, aspetta un attimo.",
"invalid-chat-message": "Messaggio chat non valido",
- "chat-message-too-long": "Chat messages can not be longer than %1 characters.",
+ "chat-message-too-long": "I messaggi in chat non possono superare i %1 caratteri.",
"cant-edit-chat-message": "Non ti è permesso di modificare questo messaggio",
"cant-remove-last-user": "Non puoi rimuovere l'ultimo utente",
"cant-delete-chat-message": "Non ti è permesso di eliminare questo messaggio",
@@ -119,7 +119,7 @@
"not-enough-reputation-to-downvote": "Non hai i privilegi per votare negativamente questo post",
"not-enough-reputation-to-flag": "Tu non hai abbastanza reputazione per segnalare questo Post",
"already-flagged": "Hai già messo marcato questo post",
- "self-vote": "You cannot vote on your own post",
+ "self-vote": "Non puoi votare la tua stessa discussione",
"reload-failed": "NodeBB ha incontrato un problema durante il ricaricamento: \"%1\". NodeBB continuerà a servire gli assets esistenti lato client, così puoi annullare quello che hai fatto prima di ricaricare.",
"registration-error": "Errore nella registrazione",
"parse-error": "Qualcosa è andato storto durante l'analisi della risposta proveniente dal server",
@@ -135,5 +135,5 @@
"invalid-home-page-route": "Percorso della pagina iniziale non valido",
"invalid-session": "Discrepanza della sessione",
"invalid-session-text": "Sembra che la tua sessione non sia più attiva, oppure non corrisponde con il server. Per favore ricarica la pagina.",
- "no-topics-selected": "No topics selected!"
+ "no-topics-selected": "Nessuna discussione selezionata!"
}
\ No newline at end of file
diff --git a/public/language/it/flags.json b/public/language/it/flags.json
index d05a5b25a8..14aaf7af02 100644
--- a/public/language/it/flags.json
+++ b/public/language/it/flags.json
@@ -27,12 +27,12 @@
"quick-links": "Quick Links",
"flagged-user": "Flagged User",
- "view-profile": "View Profile",
+ "view-profile": "Vedi Profilo",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
- "user-view": "View Profile",
- "user-edit": "Edit Profile",
+ "user-view": "Vedi Profilo",
+ "user-edit": "Modifica Profilo",
"notes": "Flag Notes",
"add-note": "Add Note",
@@ -44,21 +44,21 @@
"state-all": "All states",
"state-open": "New/Open",
- "state-wip": "Work in Progress",
- "state-resolved": "Resolved",
+ "state-wip": "Lavori in Corso",
+ "state-resolved": "Risolto",
"state-rejected": "Rejected",
- "no-assignee": "Not Assigned",
+ "no-assignee": "Non Assegnato",
"note-added": "Note Added",
- "modal-title": "Report Inappropriate Content",
+ "modal-title": "Segnala Contenuto Inappropriato",
"modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.",
"modal-reason-spam": "Spam",
- "modal-reason-offensive": "Offensive",
- "modal-reason-other": "Other (specify below)",
- "modal-reason-custom": "Reason for reporting this content...",
- "modal-submit": "Submit Report",
- "modal-submit-success": "Content has been flagged for moderation.",
- "modal-submit-confirm": "Confirm Submission",
+ "modal-reason-offensive": "Offensivo",
+ "modal-reason-other": "Altro (Specificare di seguito)",
+ "modal-reason-custom": "Motivazione della segnalazione...",
+ "modal-submit": "Invia la Segnalazione",
+ "modal-submit-success": "Il contenuto è stato segnalato.",
+ "modal-submit-confirm": "Conferma la Segnalazione",
"modal-submit-confirm-text": "You have a custom reason specified already. Are you sure you wish to submit via quick-report?",
"modal-submit-confirm-text-help": "Submitting a quick report will overwrite any custom reasons defined."
}
\ No newline at end of file
diff --git a/public/language/it/global.json b/public/language/it/global.json
index 7325219082..4279338d8d 100644
--- a/public/language/it/global.json
+++ b/public/language/it/global.json
@@ -104,6 +104,6 @@
"cookies.accept": "Ho capito!",
"cookies.learn_more": "Scopri di più",
"edited": "Modificato",
- "disabled": "Disabled",
- "select": "Select"
+ "disabled": "Disabilitato",
+ "select": "Seleziona"
}
\ No newline at end of file
diff --git a/public/language/it/groups.json b/public/language/it/groups.json
index 835ada672f..53f27c12c4 100644
--- a/public/language/it/groups.json
+++ b/public/language/it/groups.json
@@ -27,7 +27,7 @@
"details.disableJoinRequests": "Disabilita le richieste d'adesione",
"details.grant": "Concedi / Rimuovi la Proprietà",
"details.kick": "Espelli",
- "details.kick_confirm": "Are you sure you want to remove this member from the group?",
+ "details.kick_confirm": "Sei sicuro di voler rimuovere questo membro dal gruppo?",
"details.owner_options": "Amministratore del Grupo",
"details.group_name": "Nome Gruppo",
"details.member_count": "Totale Membri",
@@ -53,6 +53,6 @@
"new-group.group_name": "Nome Gruppo:",
"upload-group-cover": "Carica copertina del gruppo",
"bulk-invite-instructions": "Inserisci una lista di nomi utente da invitare in questo gruppo separati da virgole",
- "bulk-invite": "Bulk Invite",
+ "bulk-invite": "Invita in Massa",
"remove_group_cover_confirm": "Sei sicuro di voler rimuovere l'immagine copertina?"
}
\ No newline at end of file
diff --git a/public/language/it/modules.json b/public/language/it/modules.json
index 5fcef5d582..d379688576 100644
--- a/public/language/it/modules.json
+++ b/public/language/it/modules.json
@@ -20,7 +20,7 @@
"chat.three_months": "3 Mesi",
"chat.delete_message_confirm": "Sei sicuro di voler eliminare questo messaggio?",
"chat.add-users-to-room": "Aggiungi utenti alla stanza",
- "chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?",
+ "chat.confirm-chat-with-dnd-user": "Questo utente ha impostato il suo stato su Non Disturbare. Sei sicuro di voler iniziare una conversazione?",
"composer.compose": "Componi",
"composer.show_preview": "Visualizza Anteprima",
"composer.hide_preview": "Nascondi Anteprima",
diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json
index 5aa5225d1b..7a1b0c4b36 100644
--- a/public/language/it/notifications.json
+++ b/public/language/it/notifications.json
@@ -29,9 +29,9 @@
"user_flagged_post_in": "%1 ha segnalato un post in %2",
"user_flagged_post_in_dual": "%1 e %2 hanno segnalato un post in %3",
"user_flagged_post_in_multiple": "%1 ed altri %2 hanno segnalato un post in %3",
- "user_flagged_user": "%1 flagged a user profile (%2)",
- "user_flagged_user_dual": "%1 and %2 flagged a user profile (%3)",
- "user_flagged_user_multiple": "%1 and %2 others flagged a user profile (%3)",
+ "user_flagged_user": "%1 ha segnalato un utente (%2)",
+ "user_flagged_user_dual": "%1 e %2 hanno segnalato un utente (%3)",
+ "user_flagged_user_multiple": "%1 e altri %2 hanno segnalato un utente (%3)",
"user_posted_to": "%1 ha postato una risposta a: %2",
"user_posted_to_dual": "%1 e %2 hanno postato una risposta su: %3",
"user_posted_to_multiple": "%1 ed altri %2 hanno postato una risposta su: %3",
@@ -42,23 +42,23 @@
"new_register": "%1 ha inviato una richiesta di registrazione.",
"new_register_multiple": "Ci sono %1 richieste di registrazione che attendono di essere esaminate.",
"flag_assigned_to_you": "Flag %1 has been assigned to you",
- "post_awaiting_review": "Post awaiting review",
+ "post_awaiting_review": "Post in attesa di revisione",
"email-confirmed": "Email Confermata",
"email-confirmed-message": "Grazie per aver validato la tua email. Il tuo account è ora completamente attivato.",
"email-confirm-error-message": "C'è stato un problema nella validazione del tuo indirizzo email. Potrebbe essere il codice non valido o scaduto.",
"email-confirm-sent": "Email di conferma inviata.",
- "none": "None",
- "notification_only": "Notification Only",
- "email_only": "Email Only",
- "notification_and_email": "Notification & Email",
- "notificationType_upvote": "When someone upvotes your post",
+ "none": "Nessuna Notifica",
+ "notification_only": "Solo Notifiche",
+ "email_only": "Solo Email",
+ "notification_and_email": "Email e Notifica",
+ "notificationType_upvote": "Quando il tuo post riceve un Mi Piace",
"notificationType_new-topic": "When someone you follow posts a topic",
"notificationType_new-reply": "When a new reply is posted in a topic you are watching",
"notificationType_follow": "When someone starts following you",
"notificationType_new-chat": "When you receive a chat message",
"notificationType_group-invite": "When you receive a group invite",
"notificationType_new-register": "When someone gets added to registration queue",
- "notificationType_post-queue": "When a new post is queued",
- "notificationType_new-post-flag": "When a post is flagged",
- "notificationType_new-user-flag": "When a user is flagged"
+ "notificationType_post-queue": "Quando un nuovo post è in attesa di revisione",
+ "notificationType_new-post-flag": "Quando un post viene segnalato",
+ "notificationType_new-user-flag": "Quando un utente viene segnalato"
}
\ No newline at end of file
diff --git a/public/language/it/pages.json b/public/language/it/pages.json
index 0b1be5dc1e..0be5a17a98 100644
--- a/public/language/it/pages.json
+++ b/public/language/it/pages.json
@@ -7,7 +7,7 @@
"popular-alltime": "Discussioni più popolari di sempre",
"recent": "Discussioni Recenti",
"moderator-tools": "Moderator Tools",
- "flagged-content": "Flagged Content",
+ "flagged-content": "Contenuti Segnalati",
"ip-blacklist": "Lista nera degli IP",
"post-queue": "Post Queue",
"users/online": "Utenti Online",
@@ -44,7 +44,7 @@
"account/bookmarks": "%1 Post tra i favoriti",
"account/settings": "Impostazioni Utente",
"account/watched": "Discussioni osservate da %1",
- "account/ignored": "Topics ignored by %1",
+ "account/ignored": "Discussioni ignorate da %1",
"account/upvoted": "Post apprezzati da %1",
"account/downvoted": "Post votati negativamente da %1",
"account/best": "I migliori post di %1",
diff --git a/public/language/it/register.json b/public/language/it/register.json
index e3afd5aed2..49de769d43 100644
--- a/public/language/it/register.json
+++ b/public/language/it/register.json
@@ -1,5 +1,5 @@
{
- "register": "Registrazione",
+ "register": "Registrati",
"cancel_registration": "Cancella Registrazione",
"help.email": "Come opzione predefinita, il tuo indirizzo email non verrà reso pubblico.",
"help.username_restrictions": "Un nome utente unico, di almeno %1 caratteri e al massimo di %2. Gli altri utenti ti possono menzionare usando @username.",
diff --git a/public/language/it/search.json b/public/language/it/search.json
index 980e2c28ba..dc33a58737 100644
--- a/public/language/it/search.json
+++ b/public/language/it/search.json
@@ -12,7 +12,7 @@
"reply-count": "Numero Risposte",
"at-least": "Almeno",
"at-most": "Al massimo",
- "relevance": "Relevance",
+ "relevance": "Rilevanza",
"post-time": "Ora invio",
"newer-than": "Più nuovi di",
"older-than": "Più vecchi di",
diff --git a/public/language/it/success.json b/public/language/it/success.json
index cf8dd4cad5..35f6d5b2a9 100644
--- a/public/language/it/success.json
+++ b/public/language/it/success.json
@@ -1,7 +1,7 @@
{
"success": "Riuscito",
"topic-post": "Hai postato correttamente.",
- "post-queued": "Your post is queued for approval.",
+ "post-queued": "La tua discussione è in attesa di approvazione.",
"authentication-successful": "Autenticazione Riuscita",
"settings-saved": "Impostazioni salvate!"
}
\ No newline at end of file
diff --git a/public/language/it/topic.json b/public/language/it/topic.json
index 747ba4876b..5b15970770 100644
--- a/public/language/it/topic.json
+++ b/public/language/it/topic.json
@@ -13,9 +13,9 @@
"notify_me": "Ricevi notifiche di nuove risposte in questa discussione",
"quote": "Cita",
"reply": "Rispondi",
- "replies_to_this_post": "%1 Replies",
- "one_reply_to_this_post": "1 Reply",
- "last_reply_time": "Last reply",
+ "replies_to_this_post": "%1 Risposte",
+ "one_reply_to_this_post": "1 Risposta",
+ "last_reply_time": "Ultima Risposta",
"reply-as-topic": "Topic risposta",
"guest-login-reply": "Effettua il Log in per rispondere",
"edit": "Modifica",
@@ -59,7 +59,7 @@
"thread_tools.unlock": "Sblocca Discussione",
"thread_tools.move": "Sposta Discussione",
"thread_tools.move_all": "Sposta Tutto",
- "thread_tools.select_category": "Select Category",
+ "thread_tools.select_category": "Seleziona Categoria",
"thread_tools.fork": "Dividi Discussione",
"thread_tools.delete": "Elimina Discussione",
"thread_tools.delete-posts": "Cancella post",
@@ -68,8 +68,8 @@
"thread_tools.restore_confirm": "Sei sicuro di voler ripristinare questa discussione?",
"thread_tools.purge": "Svuota Discussione",
"thread_tools.purge_confirm": "Sei sicuro di voler svuotare questa discussione?",
- "thread_tools.merge_topics": "Merge Topics",
- "thread_tools.merge": "Merge",
+ "thread_tools.merge_topics": "Unisci le Discussioni",
+ "thread_tools.merge": "Unisci",
"topic_move_success": "Questa discussione è stata correttamente spostata in %1",
"post_delete_confirm": "Sei sicuro di voler cancellare questo post?",
"post_restore_confirm": "Sei sicuro di voler ripristinare questo post?",
@@ -91,7 +91,7 @@
"fork_pid_count": "%1 post selezionati",
"fork_success": "Topic Diviso con successo ! Clicca qui per andare al Topic Diviso.",
"delete_posts_instruction": "Clicca sui post che vuoi cancellare/eliminare",
- "merge_topics_instruction": "Click the topics you want to merge",
+ "merge_topics_instruction": "Clicca sulle discussioni che vuoi unire",
"composer.title_placeholder": "Inserisci qui il titolo della discussione...",
"composer.handle_placeholder": "Nome",
"composer.discard": "Annulla",
diff --git a/public/language/it/unread.json b/public/language/it/unread.json
index c1244d0fe3..2f2afebd23 100644
--- a/public/language/it/unread.json
+++ b/public/language/it/unread.json
@@ -10,6 +10,6 @@
"all-topics": "Tutte le Discussioni",
"new-topics": "Nuova Discussione",
"watched-topics": "Discussioni seguite",
- "unreplied-topics": "Nessuna Risposta",
- "multiple-categories-selected": "Multiple Selected"
+ "unreplied-topics": "Discussioni Senza Risposta",
+ "multiple-categories-selected": "Più Categorie"
}
\ No newline at end of file
diff --git a/public/language/it/user.json b/public/language/it/user.json
index a24f325d24..d3b66bfa4a 100644
--- a/public/language/it/user.json
+++ b/public/language/it/user.json
@@ -19,13 +19,13 @@
"location": "Località",
"age": "Età",
"joined": "Iscrizione",
- "lastonline": "Ultima volta in linea",
+ "lastonline": "Ultimo Accesso",
"profile": "Profilo",
"profile_views": "Visite al profilo",
"reputation": "Reputazione",
- "bookmarks": "Favoriti",
+ "bookmarks": "Preferiti",
"watched": "Osservati",
- "ignored": "Ignored",
+ "ignored": "Ignorati",
"followers": "Da chi è seguito",
"following": "Chi segue",
"aboutme": "Su di me",
@@ -61,7 +61,7 @@
"username_taken_workaround": "Il nome utente che hai richiesto era già stato utilizzato, quindi lo abbiamo modificato leggermente. Ora il tuo è %1",
"password_same_as_username": "La tua password è uguale al tuo username, per piacere scegli un'altra password",
"password_same_as_email": "La tua password sembra coincidere con la tua email, per favore fornisci un'altra password.",
- "weak_password": "Weak password.",
+ "weak_password": "Password debole.",
"upload_picture": "Carica foto",
"upload_a_picture": "Carica una foto",
"remove_uploaded_picture": "Elimina foto caricata",
@@ -85,7 +85,7 @@
"has_no_posts": "Questo utente non ha ancora scritto niente.",
"has_no_topics": "Questo utente non ha ancora avviato discussioni.",
"has_no_watched_topics": "Questo utente non sta osservando discussioni.",
- "has_no_ignored_topics": "This user hasn't ignored any topics yet.",
+ "has_no_ignored_topics": "Questo utente non sta ignorando discussioni.",
"has_no_upvoted_posts": "Questo utente non ha ancora apprezzato nessun post.",
"has_no_downvoted_posts": "Questo utente non ha ancora votato negativamente alcun post",
"has_no_voted_posts": "Questo utente non ha post votati",
@@ -94,18 +94,18 @@
"paginate_description": "Non utilizzare lo scroll infinito per discussioni e messaggi",
"topics_per_page": "Discussioni per Pagina",
"posts_per_page": "Post per Pagina",
- "max_items_per_page": "Maximum %1",
+ "max_items_per_page": "Massimo %1",
"notification_sounds": "Riproduci un suono quando si riceve una notifica",
"notifications_and_sounds": "Notifiche e Suoni",
"incoming-message-sound": "Suono messaggio in entrata",
"outgoing-message-sound": "Suono messaggio in uscita",
"notification-sound": "Suono di notifica",
"no-sound": "Nessun suono",
- "upvote-notif-freq": "Upvote Notification Frequency",
- "upvote-notif-freq.all": "All Upvotes",
- "upvote-notif-freq.everyTen": "Every Ten Upvotes",
- "upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
- "upvote-notif-freq.disabled": "Disabled",
+ "upvote-notif-freq": "Frequenza Notifiche dei Mi Piace ",
+ "upvote-notif-freq.all": "Tutti i Mi Piace",
+ "upvote-notif-freq.everyTen": "Ogni Dieci Mi Piace",
+ "upvote-notif-freq.logarithmic": "Ogni 10, 100, 1000...",
+ "upvote-notif-freq.disabled": "Disabilitate",
"browsing": "Impostazioni di Navigazione",
"open_links_in_new_tab": "Apri i link web in una nuova pagina",
"enable_topic_searching": "Abilita la ricerca negli argomenti",
diff --git a/public/language/nl/email.json b/public/language/nl/email.json
index 48f35a2c6f..bd474f945e 100644
--- a/public/language/nl/email.json
+++ b/public/language/nl/email.json
@@ -18,7 +18,7 @@
"reset.notify.text2": "Neem onmiddellijk contact met een beheerder op wanneer je hiervoor geen toestemming hebt gegeven.",
"digest.notifications": "Er zijn ongelezen notificaties van %1:",
"digest.latest_topics": "De meest recente onderwerpen van %1",
- "digest.cta": "Klik hier om deze website te bezoeken %1 ",
+ "digest.cta": "Klik hier om %1 te bezoeken ",
"digest.unsub.info": "Deze samenvatting hebben we naar je verzonden omdat je dat hebt ingesteld.",
"digest.no_topics": "In de afgelopen %1 zijn er geen actieve onderwerpen geweest.",
"digest.day": "dag",
@@ -30,7 +30,7 @@
"notif.chat.unsub.info": "Deze notificatie is verzonden vanwege de gebruikersinstellingen voor abonnementen.",
"notif.post.cta": "Klik hier om het volledige bericht te lezen",
"notif.post.unsub.info": "Deze notificatie is door ons verzonden vanwege gebruikersinstellingen voor abonnementen en berichten.",
- "notif.cta": "Click here to go to forum",
+ "notif.cta": "Klik hier om naar het forum te gaan",
"test.text1": "Dit is een testbericht om te verifiëren dat NodeBB de e-mailberichtservice correct heeft opgezet.",
"unsub.cta": "Klik hier om deze instellingen te wijzigen",
"banned.subject": "U bent verbannen van %1",
diff --git a/public/language/nl/error.json b/public/language/nl/error.json
index 460254c097..48931aa461 100644
--- a/public/language/nl/error.json
+++ b/public/language/nl/error.json
@@ -119,13 +119,13 @@
"not-enough-reputation-to-downvote": "Je hebt onvoldoende reputatie om een negatieve stem uit te mogen brengen",
"not-enough-reputation-to-flag": "Je hebt onvoldoende reputatie om dit bericht aan de beheerders te mogen melden",
"already-flagged": "Je hebt deze post al gerapporteerd",
- "self-vote": "You cannot vote on your own post",
+ "self-vote": "Het is niet mogelijk om op je eigen bericht te stemmen",
"reload-failed": "Tijdens het herladen van \"%1\" is NodeBB een fout of probleem tegengekomen. NodeBB blijft operationeel. Echter het is verstandig om de oorzaak te onderzoeken en wellicht de vorige actie, voor het herladen, ongedaan te maken.",
"registration-error": "Fout tijdens registratie",
"parse-error": "Tijdens het verwerken van het antwoord van de server is er iets misgegaan.",
"wrong-login-type-email": "Gebruik je e-mailadres om in te loggen",
"wrong-login-type-username": "Gebruik je gebruikersnaam om in te loggen",
- "sso-registration-disabled": "Registration has been disabled for %1 accounts, please register with an email address first",
+ "sso-registration-disabled": "Registratie is uitgeschakeld voor %1 accounts, registreer eerst met een e-mailadres",
"invite-maximum-met": "Je heb het maximum aantal mensen uitgenodigd (%1 van de %2).",
"no-session-found": "Geen login sessie gevonden!",
"not-in-room": "Gebruiker niet in de chat",
@@ -135,5 +135,5 @@
"invalid-home-page-route": "Onbekende homepage route",
"invalid-session": "Verkeerde sessie combinatie",
"invalid-session-text": "Het lijkt erop dat je login sessie niet meer actief is of niet langer synchroon is met de server. Ververs de pagina.",
- "no-topics-selected": "No topics selected!"
+ "no-topics-selected": "Geen onderwerpen geselecteerd!"
}
\ No newline at end of file
diff --git a/public/language/nl/notifications.json b/public/language/nl/notifications.json
index c19dbaef83..78ec3289b5 100644
--- a/public/language/nl/notifications.json
+++ b/public/language/nl/notifications.json
@@ -9,7 +9,7 @@
"continue_to": "Door naar %1",
"return_to": "Terug naar %1",
"new_notification": "Nieuwe notificatie",
- "new_notification_from": "You have a new Notification from %1",
+ "new_notification_from": "Je hebt een nieuwe notificatie van %1",
"you_have_unread_notifications": "Je hebt nieuwe notificaties.",
"all": "Alles",
"topics": "Onderwerpen",
@@ -47,18 +47,18 @@
"email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Je account is nu volledig geactiveerd.",
"email-confirm-error-message": "Er was een probleem met het bevestigen van dit e-mailadres. Misschien is de code niet goed ingevoerd of was de beschikbare tijd inmiddels verstreken.",
"email-confirm-sent": "Bevestigingsmail verstuurd.",
- "none": "None",
- "notification_only": "Notification Only",
- "email_only": "Email Only",
- "notification_and_email": "Notification & Email",
- "notificationType_upvote": "When someone upvotes your post",
- "notificationType_new-topic": "When someone you follow posts a topic",
- "notificationType_new-reply": "When a new reply is posted in a topic you are watching",
- "notificationType_follow": "When someone starts following you",
- "notificationType_new-chat": "When you receive a chat message",
- "notificationType_group-invite": "When you receive a group invite",
- "notificationType_new-register": "When someone gets added to registration queue",
- "notificationType_post-queue": "When a new post is queued",
- "notificationType_new-post-flag": "When a post is flagged",
- "notificationType_new-user-flag": "When a user is flagged"
+ "none": "Geen",
+ "notification_only": "Alleen notificatie",
+ "email_only": "Alleen e-mail",
+ "notification_and_email": "Notificatie & e-mail",
+ "notificationType_upvote": "Als iemand positief stemt voor je bericht",
+ "notificationType_new-topic": "Wanneer iemand die jij volgt een onderwerp post",
+ "notificationType_new-reply": "Als een nieuwe reactie komt op een onderwerp dat je volgt",
+ "notificationType_follow": "Als iemand begint met jou te volgen",
+ "notificationType_new-chat": "Als je een chat-bericht ontvangt",
+ "notificationType_group-invite": "Als je een uitnodiging voor een groep ontvangt",
+ "notificationType_new-register": "Als iemand wordt toegevoegd aan een registratiewachtrij",
+ "notificationType_post-queue": "Als een bericht aan de wachtrij wordt toegevoegd",
+ "notificationType_new-post-flag": "Als een bericht wordt gevlagd",
+ "notificationType_new-user-flag": "Als een gebruiker wordt gevlagd"
}
\ No newline at end of file
diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json
index b1d33fd9da..29b4eb9e75 100644
--- a/public/language/nl/topic.json
+++ b/public/language/nl/topic.json
@@ -68,8 +68,8 @@
"thread_tools.restore_confirm": "Zeker weten dit onderwerp te herstellen?",
"thread_tools.purge": "Wis onderwerp ",
"thread_tools.purge_confirm": "Weet je zeker dat je dit onderwerp wil verwijderen?",
- "thread_tools.merge_topics": "Merge Topics",
- "thread_tools.merge": "Merge",
+ "thread_tools.merge_topics": "Onderwerpen samenvoegen",
+ "thread_tools.merge": "Samenvoegen",
"topic_move_success": "Verplaatsen van onderwerp naar %1 succesvol",
"post_delete_confirm": "Is het absoluut de bedoeling dit bericht te verwijderen?",
"post_restore_confirm": "Is het de bedoeling dit bericht te herstellen?",
@@ -91,7 +91,7 @@
"fork_pid_count": "%1 bericht(en) geselecteerd",
"fork_success": "Onderwerp is succesvol afgesplitst. Klik hier om het nieuwe onderwerp te zien.",
"delete_posts_instruction": "Klik op de berichten die verwijderd moeten worden",
- "merge_topics_instruction": "Click the topics you want to merge",
+ "merge_topics_instruction": "Klik op de onderwerpen die samengevoegd moeten worden",
"composer.title_placeholder": "Voer hier de titel van het onderwerp in...",
"composer.handle_placeholder": "Naam",
"composer.discard": "Annuleren",
diff --git a/public/language/nl/user.json b/public/language/nl/user.json
index 6351ae8827..9c2af5d6f5 100644
--- a/public/language/nl/user.json
+++ b/public/language/nl/user.json
@@ -101,11 +101,11 @@
"outgoing-message-sound": "Uitgaand bericht geluid",
"notification-sound": "Notificatie geluid",
"no-sound": "Geen geluid",
- "upvote-notif-freq": "Upvote Notification Frequency",
- "upvote-notif-freq.all": "All Upvotes",
- "upvote-notif-freq.everyTen": "Every Ten Upvotes",
- "upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
- "upvote-notif-freq.disabled": "Disabled",
+ "upvote-notif-freq": "Notificatie frequentie voor Upvotes",
+ "upvote-notif-freq.all": "Alle Upvotes",
+ "upvote-notif-freq.everyTen": "Elke tien Upvotes",
+ "upvote-notif-freq.logarithmic": "Bij 10, 100, 1000...",
+ "upvote-notif-freq.disabled": "Uitgeschakeld",
"browsing": "Instellingen voor bladeren",
"open_links_in_new_tab": "Open uitgaande links naar een externe site in een nieuw tabblad",
"enable_topic_searching": "Inschakelen mogelijkheid op onderwerp te kunnen zoeken",
diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less
index 124be23b5c..d7cd7e6ae4 100644
--- a/public/less/admin/admin.less
+++ b/public/less/admin/admin.less
@@ -14,6 +14,7 @@
@import "./manage/groups";
@import "./manage/registration";
@import "./manage/users";
+@import "./manage/admins-mods";
@import "./appearance/customise";
@import "./appearance/themes";
@import "./extend/plugins";
diff --git a/public/less/admin/manage/admins-mods.less b/public/less/admin/manage/admins-mods.less
new file mode 100644
index 0000000000..0bf7ac46cd
--- /dev/null
+++ b/public/less/admin/manage/admins-mods.less
@@ -0,0 +1,27 @@
+.admins-mods {
+ .user-card {
+ margin-right: 10px;
+ padding: 2px;
+ }
+
+ .remove-user-icon {
+ margin-right: 5px;
+ margin-left: 5px;
+ }
+
+ .category-depth-1 {
+ margin-left: 30px;
+ }
+ .category-depth-2 {
+ margin-left: 60px;
+ }
+ .category-depth-3 {
+ margin-left: 90px;
+ }
+ .category-depth-4 {
+ margin-left: 120px;
+ }
+ .category-depth-5 {
+ margin-left: 150px;
+ }
+}
\ No newline at end of file
diff --git a/public/src/admin/manage/admins-mods.js b/public/src/admin/manage/admins-mods.js
new file mode 100644
index 0000000000..9f642f1456
--- /dev/null
+++ b/public/src/admin/manage/admins-mods.js
@@ -0,0 +1,142 @@
+'use strict';
+
+define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], function (translator, Benchpress, autocomplete) {
+ var AdminsMods = {};
+
+ AdminsMods.init = function () {
+ autocomplete.user($('#admin-search'), function (ev, ui) {
+ socket.emit('admin.user.makeAdmins', [ui.item.user.uid], function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/users:alerts.make-admin-success]]');
+ $('#admin-search').val('');
+
+ if ($('.administrator-area [data-uid="' + ui.item.user.uid + '"]').length) {
+ return;
+ }
+
+ app.parseAndTranslate('admin/manage/admins-mods', 'admins.members', { admins: { members: [ui.item.user] } }, function (html) {
+ $('.administrator-area').prepend(html);
+ });
+ });
+ });
+
+ $('.administrator-area').on('click', '.remove-user-icon', function () {
+ var userCard = $(this).parents('[data-uid]');
+ var uid = userCard.attr('data-uid');
+ if (parseInt(uid, 10) === parseInt(app.user.uid, 10)) {
+ return app.alertError('[[admin/manage/users:alerts.no-remove-yourself-admin]]');
+ }
+ bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-admin]]', function (confirm) {
+ if (confirm) {
+ socket.emit('admin.user.removeAdmins', [uid], function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/users:alerts.remove-admin-success]]');
+ userCard.remove();
+ });
+ }
+ });
+ });
+
+ autocomplete.user($('#global-mod-search'), function (ev, ui) {
+ socket.emit('admin.groups.join', {
+ groupName: 'Global Moderators',
+ uid: ui.item.user.uid,
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/users:alerts.make-global-mod-success]]');
+ $('#global-mod-search').val('');
+
+ if ($('.global-moderator-area [data-uid="' + ui.item.user.uid + '"]').length) {
+ return;
+ }
+
+ app.parseAndTranslate('admin/manage/admins-mods', 'globalMods.members', { globalMods: { members: [ui.item.user] } }, function (html) {
+ $('.global-moderator-area').prepend(html);
+ $('#no-global-mods-warning').addClass('hidden');
+ });
+ });
+ });
+
+ $('.global-moderator-area').on('click', '.remove-user-icon', function () {
+ var userCard = $(this).parents('[data-uid]');
+ var uid = userCard.attr('data-uid');
+
+ bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-global-mod]]', function (confirm) {
+ if (confirm) {
+ socket.emit('admin.groups.leave', { uid: uid, groupName: 'Global Moderators' }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/users:alerts.remove-global-mod-success]]');
+ userCard.remove();
+ if (!$('.global-moderator-area').children().length) {
+ $('#no-global-mods-warning').removeClass('hidden');
+ }
+ });
+ }
+ });
+ });
+
+
+ autocomplete.user($('.moderator-search'), function (ev, ui) {
+ var input = $(ev.target);
+ var cid = $(ev.target).attr('data-cid');
+ socket.emit('admin.categories.setPrivilege', {
+ cid: cid,
+ privilege: ['moderate'],
+ set: true,
+ member: ui.item.user.uid,
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/users:alerts.make-moderator-success]]');
+ input.val('');
+
+ if ($('.moderator-area[data-cid="' + cid + '"] [data-uid="' + ui.item.user.uid + '"]').length) {
+ return;
+ }
+
+ app.parseAndTranslate('admin/manage/admins-mods', 'globalMods', { globalMods: [ui.item.user] }, function (html) {
+ $('.moderator-area[data-cid="' + cid + '"]').prepend(html);
+ $('.no-moderator-warning[data-cid="' + cid + '"]').addClass('hidden');
+ });
+ });
+ });
+
+ $('.moderator-area').on('click', '.remove-user-icon', function () {
+ var moderatorArea = $(this).parents('[data-cid]');
+ var cid = moderatorArea.attr('data-cid');
+ var userCard = $(this).parents('[data-uid]');
+ var uid = userCard.attr('data-uid');
+
+ bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-moderator]]', function (confirm) {
+ if (confirm) {
+ socket.emit('admin.categories.setPrivilege', {
+ cid: cid,
+ privilege: ['moderate'],
+ set: false,
+ member: uid,
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/users:alerts.remove-moderator-success]]');
+ userCard.remove();
+ if (!moderatorArea.children().length) {
+ $('.no-moderator-warning[data-cid="' + cid + '"]').removeClass('hidden');
+ }
+ });
+ }
+ });
+ });
+ };
+
+ return AdminsMods;
+});
diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js
index 8ac0ecdf15..fb4ae062b8 100644
--- a/public/src/admin/manage/category.js
+++ b/public/src/admin/manage/category.js
@@ -1,6 +1,5 @@
'use strict';
-
define('admin/manage/category', [
'uploader',
'iconSelect',
@@ -8,8 +7,7 @@ define('admin/manage/category', [
'autocomplete',
'translator',
'categorySelector',
- 'benchpress',
-], function (uploader, iconSelect, colorpicker, autocomplete, translator, categorySelector, Benchpress) {
+], function (uploader, iconSelect, colorpicker, autocomplete, translator, categorySelector) {
var Category = {};
var modified_categories = {};
@@ -100,7 +98,7 @@ define('admin/manage/category', [
});
$('.copy-settings').on('click', function () {
- selectCategoryModal(function (cid) {
+ categorySelector.modal(function (cid) {
socket.emit('admin.categories.copySettingsFrom', { fromCid: cid, toCid: ajaxify.data.category.cid }, function (err) {
if (err) {
return app.alertError(err.message);
@@ -169,8 +167,6 @@ define('admin/manage/category', [
$('button[data-action="setParent"]').removeClass('hide');
});
});
-
- Category.setupPrivilegeTable();
};
function modified(el) {
@@ -208,102 +204,12 @@ define('admin/manage/category', [
});
}
- Category.setupPrivilegeTable = function () {
- $('.privilege-table-container').on('change', 'input[type="checkbox"]', function () {
- var checkboxEl = $(this);
- var privilege = checkboxEl.parent().attr('data-privilege');
- var state = checkboxEl.prop('checked');
- var rowEl = checkboxEl.parents('tr');
- var member = rowEl.attr('data-group-name') || rowEl.attr('data-uid');
- var isPrivate = parseInt(rowEl.attr('data-private') || 0, 10);
- var isGroup = rowEl.attr('data-group-name') !== undefined;
-
- if (member) {
- if (isGroup && privilege === 'groups:moderate' && !isPrivate && state) {
- bootbox.confirm('[[admin/manage/categories:alert.confirm-moderate]]', function (confirm) {
- if (confirm) {
- Category.setPrivilege(member, privilege, state, checkboxEl);
- } else {
- checkboxEl.prop('checked', !checkboxEl.prop('checked'));
- }
- });
- } else {
- Category.setPrivilege(member, privilege, state, checkboxEl);
- }
- } else {
- app.alertError('[[error:invalid-data]]');
- }
- });
-
- $('.privilege-table-container').on('click', '[data-action="search.user"]', Category.addUserToPrivilegeTable);
- $('.privilege-table-container').on('click', '[data-action="search.group"]', Category.addGroupToPrivilegeTable);
- $('.privilege-table-container').on('click', '[data-action="copyToChildren"]', Category.copyPrivilegesToChildren);
- $('.privilege-table-container').on('click', '[data-action="copyPrivilegesFrom"]', Category.copyPrivilegesFromCategory);
-
- Category.exposeAssumedPrivileges();
- };
-
- Category.refreshPrivilegeTable = function () {
- socket.emit('admin.categories.getPrivilegeSettings', ajaxify.data.category.cid, function (err, privileges) {
- if (err) {
- return app.alertError(err.message);
- }
-
- Benchpress.parse('admin/partials/categories/privileges', {
- privileges: privileges,
- }, function (html) {
- translator.translate(html, function (html) {
- $('.privilege-table-container').html(html);
- Category.exposeAssumedPrivileges();
- });
- });
- });
- };
-
- Category.exposeAssumedPrivileges = function () {
- /*
- If registered-users has a privilege enabled, then all users and groups of that privilege
- should be assumed to have that privilege as well, even if not set in the db, so reflect
- this arrangement in the table
- */
- var privs = [];
- $('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]').parent().each(function (idx, el) {
- if ($(el).find('input').prop('checked')) {
- privs.push(el.getAttribute('data-privilege'));
- }
- });
- for (var x = 0, numPrivs = privs.length; x < numPrivs; x += 1) {
- var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"]) td[data-privilege="' + privs[x] + '"] input');
- inputs.each(function (idx, el) {
- if (!el.checked) {
- el.indeterminate = true;
- }
- });
- }
- };
-
- Category.setPrivilege = function (member, privilege, state, checkboxEl) {
- socket.emit('admin.categories.setPrivilege', {
- cid: ajaxify.data.category.cid,
- privilege: privilege,
- set: state,
- member: member,
- }, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
-
- checkboxEl.replaceWith('');
- Category.refreshPrivilegeTable();
- });
- };
-
Category.launchParentSelector = function () {
var categories = ajaxify.data.allCategories.filter(function (category) {
return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10);
});
- selectCategoryModal(categories, function (parentCid) {
+ categorySelector.modal(categories, function (parentCid) {
var payload = {};
payload[ajaxify.data.category.cid] = {
@@ -327,117 +233,5 @@ define('admin/manage/category', [
});
};
- Category.addUserToPrivilegeTable = function () {
- var modal = bootbox.dialog({
- title: '[[admin/manage/categories:alert.find-user]]',
- message: '',
- show: true,
- });
-
- modal.on('shown.bs.modal', function () {
- var inputEl = modal.find('input');
-
- autocomplete.user(inputEl, function (ev, ui) {
- socket.emit('admin.categories.setPrivilege', {
- cid: ajaxify.data.category.cid,
- privilege: ['find', 'read', 'topics:read'],
- set: true,
- member: ui.item.user.uid,
- }, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
-
- Category.refreshPrivilegeTable();
- modal.modal('hide');
- });
- });
- });
- };
-
- Category.addGroupToPrivilegeTable = function () {
- var modal = bootbox.dialog({
- title: '[[admin/manage/categories:alert.find-group]]',
- message: '',
- show: true,
- });
-
- modal.on('shown.bs.modal', function () {
- var inputEl = modal.find('input');
-
- autocomplete.group(inputEl, function (ev, ui) {
- socket.emit('admin.categories.setPrivilege', {
- cid: ajaxify.data.category.cid,
- privilege: ['groups:find', 'groups:read', 'groups:topics:read'],
- set: true,
- member: ui.item.group.name,
- }, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
-
- Category.refreshPrivilegeTable();
- modal.modal('hide');
- });
- });
- });
- };
-
- Category.copyPrivilegesToChildren = function () {
- socket.emit('admin.categories.copyPrivilegesToChildren', ajaxify.data.category.cid, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
- app.alertSuccess('Privileges copied!');
- });
- };
-
- Category.copyPrivilegesFromCategory = function () {
- selectCategoryModal(function (cid) {
- socket.emit('admin.categories.copyPrivilegesFrom', { toCid: ajaxify.data.category.cid, fromCid: cid }, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
- ajaxify.refresh();
- });
- });
- };
-
- function selectCategoryModal(categories, callback) {
- if (typeof categories === 'function') {
- callback = categories;
- categories = ajaxify.data.allCategories;
- }
- Benchpress.parse('admin/partials/categories/select-category', {
- categories: categories,
- }, function (html) {
- translator.translate(html, function (html) {
- var modal = bootbox.dialog({
- title: '[[modules:composer.select_category]]',
- message: html,
- buttons: {
- save: {
- label: '[[global:select]]',
- className: 'btn-primary',
- callback: submit,
- },
- },
- });
- categorySelector.init(modal.find('[component="category-selector"]'));
- function submit(ev) {
- ev.preventDefault();
- var selectedCategory = categorySelector.getSelectedCategory();
- if (selectedCategory) {
- callback(selectedCategory.cid);
- modal.modal('hide');
- }
- return false;
- }
-
- modal.find('form').on('submit', submit);
- });
- });
- }
-
return Category;
});
diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js
new file mode 100644
index 0000000000..74aea3a195
--- /dev/null
+++ b/public/src/admin/manage/privileges.js
@@ -0,0 +1,194 @@
+'use strict';
+
+define('admin/manage/privileges', [
+ 'autocomplete',
+ 'translator',
+ 'benchpress',
+ 'categorySelector',
+], function (autocomplete, translator, Benchpress, categorySelector) {
+ var Privileges = {};
+
+ var cid;
+
+ Privileges.init = function () {
+ cid = ajaxify.data.cid || 0;
+
+ $('#category-selector').on('change', function () {
+ var val = $(this).val();
+ ajaxify.go('admin/manage/privileges/' + (val === 'global' ? '' : $(this).val()));
+ });
+
+
+ Privileges.setupPrivilegeTable();
+ };
+
+ Privileges.setupPrivilegeTable = function () {
+ $('.privilege-table-container').on('change', 'input[type="checkbox"]', function () {
+ var checkboxEl = $(this);
+ var privilege = checkboxEl.parent().attr('data-privilege');
+ var state = checkboxEl.prop('checked');
+ var rowEl = checkboxEl.parents('tr');
+ var member = rowEl.attr('data-group-name') || rowEl.attr('data-uid');
+ var isPrivate = parseInt(rowEl.attr('data-private') || 0, 10);
+ var isGroup = rowEl.attr('data-group-name') !== undefined;
+
+ if (member) {
+ if (isGroup && privilege === 'groups:moderate' && !isPrivate && state) {
+ bootbox.confirm('[[admin/manage/categories:alert.confirm-moderate]]', function (confirm) {
+ if (confirm) {
+ Privileges.setPrivilege(member, privilege, state, checkboxEl);
+ } else {
+ checkboxEl.prop('checked', !checkboxEl.prop('checked'));
+ }
+ });
+ } else {
+ Privileges.setPrivilege(member, privilege, state, checkboxEl);
+ }
+ } else {
+ app.alertError('[[error:invalid-data]]');
+ }
+ });
+
+ $('.privilege-table-container').on('click', '[data-action="search.user"]', Privileges.addUserToPrivilegeTable);
+ $('.privilege-table-container').on('click', '[data-action="search.group"]', Privileges.addGroupToPrivilegeTable);
+ $('.privilege-table-container').on('click', '[data-action="copyToChildren"]', Privileges.copyPrivilegesToChildren);
+ $('.privilege-table-container').on('click', '[data-action="copyPrivilegesFrom"]', Privileges.copyPrivilegesFromCategory);
+
+ Privileges.exposeAssumedPrivileges();
+ };
+
+ Privileges.refreshPrivilegeTable = function () {
+ socket.emit('admin.categories.getPrivilegeSettings', cid, function (err, privileges) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ var tpl = cid ? 'admin/partials/categories/privileges' : 'admin/partials/global/privileges';
+ Benchpress.parse(tpl, {
+ privileges: privileges,
+ }, function (html) {
+ translator.translate(html, function (html) {
+ $('.privilege-table-container').html(html);
+ Privileges.exposeAssumedPrivileges();
+ });
+ });
+ });
+ };
+
+ Privileges.exposeAssumedPrivileges = function () {
+ /*
+ If registered-users has a privilege enabled, then all users and groups of that privilege
+ should be assumed to have that privilege as well, even if not set in the db, so reflect
+ this arrangement in the table
+ */
+ var privs = [];
+ $('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]').parent().each(function (idx, el) {
+ if ($(el).find('input').prop('checked')) {
+ privs.push(el.getAttribute('data-privilege'));
+ }
+ });
+ for (var x = 0, numPrivs = privs.length; x < numPrivs; x += 1) {
+ var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"]) td[data-privilege="' + privs[x] + '"] input');
+ inputs.each(function (idx, el) {
+ if (!el.checked) {
+ el.indeterminate = true;
+ }
+ });
+ }
+ };
+
+ Privileges.setPrivilege = function (member, privilege, state, checkboxEl) {
+ socket.emit('admin.categories.setPrivilege', {
+ cid: cid,
+ privilege: privilege,
+ set: state,
+ member: member,
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ checkboxEl.replaceWith('');
+ Privileges.refreshPrivilegeTable();
+ });
+ };
+
+ Privileges.addUserToPrivilegeTable = function () {
+ var modal = bootbox.dialog({
+ title: '[[admin/manage/categories:alert.find-user]]',
+ message: '',
+ show: true,
+ });
+
+ modal.on('shown.bs.modal', function () {
+ var inputEl = modal.find('input');
+
+ autocomplete.user(inputEl, function (ev, ui) {
+ var defaultPrivileges = cid ? ['find', 'read', 'topics:read'] : ['chat'];
+ socket.emit('admin.categories.setPrivilege', {
+ cid: cid,
+ privilege: defaultPrivileges,
+ set: true,
+ member: ui.item.user.uid,
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ Privileges.refreshPrivilegeTable();
+ modal.modal('hide');
+ });
+ });
+ });
+ };
+
+ Privileges.addGroupToPrivilegeTable = function () {
+ var modal = bootbox.dialog({
+ title: '[[admin/manage/categories:alert.find-group]]',
+ message: '',
+ show: true,
+ });
+
+ modal.on('shown.bs.modal', function () {
+ var inputEl = modal.find('input');
+
+ autocomplete.group(inputEl, function (ev, ui) {
+ var defaultPrivileges = cid ? ['groups:find', 'groups:read', 'groups:topics:read'] : ['groups:chat'];
+ socket.emit('admin.categories.setPrivilege', {
+ cid: cid,
+ privilege: defaultPrivileges,
+ set: true,
+ member: ui.item.group.name,
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+
+ Privileges.refreshPrivilegeTable();
+ modal.modal('hide');
+ });
+ });
+ });
+ };
+
+ Privileges.copyPrivilegesToChildren = function () {
+ socket.emit('admin.categories.copyPrivilegesToChildren', cid, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('Privileges copied!');
+ });
+ };
+
+ Privileges.copyPrivilegesFromCategory = function () {
+ categorySelector.modal(function (fromCid) {
+ socket.emit('admin.categories.copyPrivilegesFrom', { toCid: cid, fromCid: fromCid }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ ajaxify.refresh();
+ });
+ });
+ };
+
+ return Privileges;
+});
diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js
index b6c7b7aa03..96da2ef057 100644
--- a/public/src/admin/manage/users.js
+++ b/public/src/admin/manage/users.js
@@ -127,36 +127,6 @@ define('admin/manage/users', ['translator', 'benchpress'], function (translator,
socket.emit('admin.user.resetLockouts', uids, done('[[admin/manage/users:alerts.lockout-reset-success]]'));
});
- $('.admin-user').on('click', function () {
- var uids = getSelectedUids();
- if (!uids.length) {
- return;
- }
-
- if (uids.indexOf(app.user.uid.toString()) !== -1) {
- app.alertError('[[admin/manage/users:alerts.no-remove-yourself-admin]]');
- } else {
- socket.emit('admin.user.makeAdmins', uids, done('[[admin/manage/users:alerts.make-admin-success]]', '.administrator', true));
- }
- });
-
- $('.remove-admin-user').on('click', function () {
- var uids = getSelectedUids();
- if (!uids.length) {
- return;
- }
-
- if (uids.indexOf(app.user.uid.toString()) !== -1) {
- app.alertError('[[admin/manage/users:alerts.no-remove-yourself-admin]]');
- } else {
- bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-admin]]', function (confirm) {
- if (confirm) {
- socket.emit('admin.user.removeAdmins', uids, done('[[admin/manage/users:alerts.remove-admin-success]]', '.administrator', false));
- }
- });
- }
- });
-
$('.validate-email').on('click', function () {
var uids = getSelectedUids();
if (!uids.length) {
diff --git a/public/src/client/account/edit/password.js b/public/src/client/account/edit/password.js
index 44723014ce..1585a85577 100644
--- a/public/src/client/account/edit/password.js
+++ b/public/src/client/account/edit/password.js
@@ -63,7 +63,7 @@ define('forum/account/edit/password', ['forum/account/header', 'translator', 'zx
onPasswordConfirmChanged();
var btn = $(this);
- if ((passwordvalid && passwordsmatch) || app.user.isAdmin) {
+ if (passwordvalid && passwordsmatch) {
btn.addClass('disabled').find('i').removeClass('hide');
socket.emit('user.changePassword', {
currentPassword: currentPassword.val(),
diff --git a/public/src/client/chats.js b/public/src/client/chats.js
index 59e923fb8a..b0ce8883ce 100644
--- a/public/src/client/chats.js
+++ b/public/src/client/chats.js
@@ -335,7 +335,7 @@ define('forum/chats', [
ajaxify.data = payload;
Chats.setActive();
Chats.addEventListeners();
-
+ messages.scrollToBottom($('.expanded-chat ul'));
if (history.pushState) {
history.pushState({
url: 'user/' + payload.userslug + '/chats/' + payload.roomId,
diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js
index 68cabdb45b..6c32cb36f2 100644
--- a/public/src/modules/autocomplete.js
+++ b/public/src/modules/autocomplete.js
@@ -29,6 +29,11 @@ define('autocomplete', function () {
uid: user.uid,
name: user.username,
slug: user.userslug,
+ username: user.username,
+ userslug: user.userslug,
+ picture: user.picture,
+ 'icon:text': user['icon:text'],
+ 'icon:bgColor': user['icon:bgColor'],
},
};
});
diff --git a/public/src/modules/categorySelector.js b/public/src/modules/categorySelector.js
index 882206a42d..b9b8c9e3a4 100644
--- a/public/src/modules/categorySelector.js
+++ b/public/src/modules/categorySelector.js
@@ -1,7 +1,6 @@
'use strict';
-
-define('categorySelector', function () {
+define('categorySelector', ['benchpress', 'translator'], function (Benchpress, translator) {
var categorySelector = {};
var selectedCategory;
var el;
@@ -29,6 +28,42 @@ define('categorySelector', function () {
el.find('[component="category-selector-selected"]').html(categoryEl.find('[component="category-markup"]').html());
};
+ categorySelector.modal = function (categories, callback) {
+ if (typeof categories === 'function') {
+ callback = categories;
+ categories = ajaxify.data.allCategories;
+ }
+ Benchpress.parse('admin/partials/categories/select-category', {
+ categories: categories,
+ }, function (html) {
+ translator.translate(html, function (html) {
+ var modal = bootbox.dialog({
+ title: '[[modules:composer.select_category]]',
+ message: html,
+ buttons: {
+ save: {
+ label: '[[global:select]]',
+ className: 'btn-primary',
+ callback: submit,
+ },
+ },
+ });
+ categorySelector.init(modal.find('[component="category-selector"]'));
+ function submit(ev) {
+ ev.preventDefault();
+ var selectedCategory = categorySelector.getSelectedCategory();
+ if (selectedCategory) {
+ callback(selectedCategory.cid);
+ modal.modal('hide');
+ }
+ return false;
+ }
+
+ modal.find('form').on('submit', submit);
+ });
+ });
+ };
+
return categorySelector;
});
diff --git a/src/categories.js b/src/categories.js
index 6013091050..452762ae38 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -338,7 +338,7 @@ Categories.buildForSelect = function (uid, privilege, callback) {
};
Categories.buildForSelectCategories = function (categories, callback) {
- function recursive(category, categoriesData, level) {
+ function recursive(category, categoriesData, level, depth) {
if (category.link) {
return;
}
@@ -347,10 +347,11 @@ Categories.buildForSelectCategories = function (categories, callback) {
category.value = category.cid;
category.level = level;
category.text = level + bullet + category.name;
+ category.depth = depth;
categoriesData.push(category);
category.children.forEach(function (child) {
- recursive(child, categoriesData, ' ' + level);
+ recursive(child, categoriesData, ' ' + level, depth + 1);
});
}
@@ -361,7 +362,7 @@ Categories.buildForSelectCategories = function (categories, callback) {
});
categories.forEach(function (category) {
- recursive(category, categoriesData, '');
+ recursive(category, categoriesData, '', 0);
});
callback(null, categoriesData);
};
diff --git a/src/categories/create.js b/src/categories/create.js
index d4a74084d9..9cd698b6f3 100644
--- a/src/categories/create.js
+++ b/src/categories/create.js
@@ -62,7 +62,6 @@ module.exports = function (Categories) {
'posts:edit',
'posts:delete',
'topics:delete',
- 'upload:post:image',
];
async.series([
diff --git a/src/categories/data.js b/src/categories/data.js
index 73c9300902..a1a9d5c587 100644
--- a/src/categories/data.js
+++ b/src/categories/data.js
@@ -31,7 +31,11 @@ module.exports = function (Categories) {
category.name = validator.escape(String(category.name || ''));
category.disabled = category.hasOwnProperty('disabled') ? parseInt(category.disabled, 10) === 1 : undefined;
category.isSection = category.hasOwnProperty('isSection') ? parseInt(category.isSection, 10) === 1 : undefined;
- category.icon = category.icon || 'hidden';
+
+ if (category.hasOwnProperty('icon')) {
+ category.icon = category.icon || 'hidden';
+ }
+
if (category.hasOwnProperty('post_count')) {
category.post_count = category.post_count || 0;
category.totalPostCount = category.post_count;
diff --git a/src/cli/package-install.js b/src/cli/package-install.js
index 5f6f9917a5..e923094b05 100644
--- a/src/cli/package-install.js
+++ b/src/cli/package-install.js
@@ -30,7 +30,7 @@ function updatePackageFile() {
exports.updatePackageFile = updatePackageFile;
function installAll() {
- process.stdout.write('\n');
+ process.stdout.write(' started\n'.green);
var prod = global.env !== 'development';
var command = 'npm install';
diff --git a/src/cli/upgrade-plugins.js b/src/cli/upgrade-plugins.js
index 3be00cb5d1..a61a711bf7 100644
--- a/src/cli/upgrade-plugins.js
+++ b/src/cli/upgrade-plugins.js
@@ -212,7 +212,7 @@ function upgradePlugins(callback) {
});
} else {
console.log('Package upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade -p'.green + '".'.reset);
- callback(null, true);
+ callback();
}
});
});
diff --git a/src/cli/upgrade.js b/src/cli/upgrade.js
index e5ab2b6c0c..2462f1f168 100644
--- a/src/cli/upgrade.js
+++ b/src/cli/upgrade.js
@@ -16,6 +16,7 @@ var steps = {
handler: function (next) {
packageInstall.updatePackageFile();
packageInstall.preserveExtraneousPlugins();
+ process.stdout.write(' OK\n'.green);
next();
},
},
@@ -54,11 +55,8 @@ function runSteps(tasks) {
tasks = tasks.map(function (key, i) {
return function (next) {
process.stdout.write('\n' + ((i + 1) + '. ').bold + steps[key].message.yellow);
- return steps[key].handler(function (err, inhibitOk) {
+ return steps[key].handler(function (err) {
if (err) { return next(err); }
- if (!inhibitOk) {
- process.stdout.write(' OK'.green + '\n'.reset);
- }
next();
});
};
diff --git a/src/controllers/accounts/chats.js b/src/controllers/accounts/chats.js
index c3b9990c26..ff5a07d157 100644
--- a/src/controllers/accounts/chats.js
+++ b/src/controllers/accounts/chats.js
@@ -5,6 +5,7 @@ var async = require('async');
var messaging = require('../../messaging');
var meta = require('../../meta');
var user = require('../../user');
+var privileges = require('../../privileges');
var helpers = require('../helpers');
var chatsController = module.exports;
@@ -26,6 +27,13 @@ chatsController.get = function (req, res, callback) {
if (!uid) {
return callback();
}
+
+ privileges.global.can('chat', req.uid, next);
+ },
+ function (canChat, next) {
+ if (!canChat) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
messaging.getRecentChats(req.uid, uid, 0, 19, next);
},
function (_recentChats, next) {
diff --git a/src/controllers/admin.js b/src/controllers/admin.js
index 6ef000fa14..dc3b6862ae 100644
--- a/src/controllers/admin.js
+++ b/src/controllers/admin.js
@@ -3,6 +3,8 @@
var adminController = {
dashboard: require('./admin/dashboard'),
categories: require('./admin/categories'),
+ privileges: require('./admin/privileges'),
+ adminsMods: require('./admin/admins-mods'),
tags: require('./admin/tags'),
postQueue: require('./admin/postqueue'),
blacklist: require('./admin/blacklist'),
diff --git a/src/controllers/admin/admins-mods.js b/src/controllers/admin/admins-mods.js
new file mode 100644
index 0000000000..97d5828c16
--- /dev/null
+++ b/src/controllers/admin/admins-mods.js
@@ -0,0 +1,50 @@
+'use strict';
+
+var async = require('async');
+
+var groups = require('../../groups');
+var categories = require('../../categories');
+
+var AdminsMods = module.exports;
+
+AdminsMods.get = function (req, res, next) {
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ admins: function (next) {
+ groups.get('administrators', { uid: req.uid }, next);
+ },
+ globalMods: function (next) {
+ groups.get('Global Moderators', { uid: req.uid }, next);
+ },
+ categories: function (next) {
+ getModeratorsOfCategories(req.uid, next);
+ },
+ }, next);
+ },
+ function (results) {
+ res.render('admin/manage/admins-mods', results);
+ },
+ ], next);
+};
+
+function getModeratorsOfCategories(uid, callback) {
+ async.waterfall([
+ function (next) {
+ categories.buildForSelect(uid, 'find', next);
+ },
+ function (categoryData, next) {
+ async.map(categoryData, function (category, next) {
+ async.waterfall([
+ function (next) {
+ categories.getModerators(category.cid, next);
+ },
+ function (moderators, next) {
+ category.moderators = moderators;
+ next(null, category);
+ },
+ ], next);
+ }, next);
+ },
+ ], callback);
+}
diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js
index 0b78912644..e03b51745c 100644
--- a/src/controllers/admin/categories.js
+++ b/src/controllers/admin/categories.js
@@ -3,7 +3,6 @@
var async = require('async');
var categories = require('../../categories');
-var privileges = require('../../privileges');
var analytics = require('../../analytics');
var plugins = require('../../plugins');
var translator = require('../../translator');
@@ -15,7 +14,6 @@ categoriesController.get = function (req, res, callback) {
function (next) {
async.parallel({
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
- privileges: async.apply(privileges.categories.list, req.params.category_id),
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
}, next);
},
@@ -36,7 +34,6 @@ categoriesController.get = function (req, res, callback) {
req: req,
res: res,
category: category,
- privileges: data.privileges,
allCategories: data.allCategories,
}, next);
},
@@ -44,7 +41,6 @@ categoriesController.get = function (req, res, callback) {
data.category.name = translator.escape(String(data.category.name));
res.render('admin/manage/category', {
category: data.category,
- privileges: data.privileges,
allCategories: data.allCategories,
});
},
diff --git a/src/controllers/admin/privileges.js b/src/controllers/admin/privileges.js
new file mode 100644
index 0000000000..92dbe27ef9
--- /dev/null
+++ b/src/controllers/admin/privileges.js
@@ -0,0 +1,39 @@
+'use strict';
+
+var async = require('async');
+
+var categories = require('../../categories');
+var privileges = require('../../privileges');
+
+var privilegesController = module.exports;
+
+privilegesController.get = function (req, res, callback) {
+ var cid = req.params.cid ? req.params.cid : 0;
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ privileges: function (next) {
+ if (!cid) {
+ privileges.global.list(next);
+ } else {
+ privileges.categories.list(cid, next);
+ }
+ },
+ allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
+ }, next);
+ },
+ function (data) {
+ data.allCategories.forEach(function (category) {
+ if (category) {
+ category.selected = parseInt(category.cid, 10) === parseInt(cid, 10);
+ }
+ });
+
+ res.render('admin/manage/privileges', {
+ privileges: data.privileges,
+ allCategories: data.allCategories,
+ cid: cid,
+ });
+ },
+ ], callback);
+};
diff --git a/src/controllers/home.js b/src/controllers/home.js
index 6c67e7aaa2..35a6cfe6a0 100644
--- a/src/controllers/home.js
+++ b/src/controllers/home.js
@@ -1,61 +1,56 @@
'use strict';
+var async = require('async');
var plugins = require('../plugins');
var meta = require('../meta');
var user = require('../user');
-var pubsub = require('../pubsub');
-var adminHomePageRoute;
-var getRoute;
-
-function configUpdated() {
- adminHomePageRoute = (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
- getRoute = parseInt(meta.config.allowUserHomePage, 10) ? getRouteAllowUserHomePage : getRouteDisableUserHomePage;
-}
-
-function getRouteDisableUserHomePage(uid, next) {
- next(null, adminHomePageRoute);
+function adminHomePageRoute() {
+ return (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
}
-function getRouteAllowUserHomePage(uid, next) {
- user.getSettings(uid, function (err, settings) {
- if (err) {
- return next(err);
- }
-
- var route = adminHomePageRoute;
-
- if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
- route = settings.homePageRoute || route;
- }
-
- next(null, route);
- });
+function getUserHomeRoute(uid, callback) {
+ async.waterfall([
+ function (next) {
+ user.getSettings(uid, next);
+ },
+ function (settings, next) {
+ var route = adminHomePageRoute();
+
+ if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
+ route = settings.homePageRoute || route;
+ }
+
+ next(null, route);
+ },
+ ], callback);
}
-pubsub.on('config:update', configUpdated);
-configUpdated();
-
function rewrite(req, res, next) {
if (req.path !== '/' && req.path !== '/api/' && req.path !== '/api') {
return next();
}
- getRoute(req.uid, function (err, route) {
- if (err) {
- return next(err);
- }
-
- var hook = 'action:homepage.get:' + route;
-
- if (!plugins.hasListeners(hook)) {
- req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
- } else {
- res.locals.homePageRoute = route;
- }
-
- next();
- });
+ async.waterfall([
+ function (next) {
+ if (parseInt(meta.config.allowUserHomePage, 10)) {
+ getUserHomeRoute(req.uid, next);
+ } else {
+ next(null, adminHomePageRoute());
+ }
+ },
+ function (route, next) {
+ var hook = 'action:homepage.get:' + route;
+
+ if (!plugins.hasListeners(hook)) {
+ req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
+ } else {
+ res.locals.homePageRoute = route;
+ }
+
+ next();
+ },
+ ], next);
}
exports.rewrite = rewrite;
diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js
index 0a91cd5dcc..e7e77c4a4f 100644
--- a/src/controllers/uploads.js
+++ b/src/controllers/uploads.js
@@ -37,9 +37,6 @@ uploadsController.upload = function (req, res, filesIterator) {
uploadsController.uploadPost = function (req, res, next) {
uploadsController.upload(req, res, function (uploadedFile, next) {
- if (!parseInt(req.body.cid, 10)) {
- return next(new Error('[[error:category-not-selected]]'));
- }
var isImage = uploadedFile.type.match(/image./);
if (isImage) {
uploadAsImage(req, uploadedFile, next);
@@ -52,7 +49,7 @@ uploadsController.uploadPost = function (req, res, next) {
function uploadAsImage(req, uploadedFile, callback) {
async.waterfall([
function (next) {
- privileges.categories.can('upload:post:image', req.body.cid, req.uid, next);
+ privileges.global.can('upload:post:image', req.uid, next);
},
function (canUpload, next) {
if (!canUpload) {
@@ -82,7 +79,7 @@ function uploadAsImage(req, uploadedFile, callback) {
function uploadAsFile(req, uploadedFile, callback) {
async.waterfall([
function (next) {
- privileges.categories.can('upload:post:file', req.body.cid, req.uid, next);
+ privileges.global.can('upload:post:file', req.uid, next);
},
function (canUpload, next) {
if (!canUpload) {
diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js
index 278ae6c413..b8ceaa4f6f 100644
--- a/src/database/mongo/main.js
+++ b/src/database/mongo/main.js
@@ -83,8 +83,8 @@ module.exports = function (db, module) {
if (!key) {
return callback();
}
- db.collection('objects').findAndModify({ _key: key }, {}, { $inc: { value: 1 } }, { new: true, upsert: true }, function (err, result) {
- callback(err, result && result.value ? result.value.value : null);
+ db.collection('objects').findAndModify({ _key: key }, {}, { $inc: { data: 1 } }, { new: true, upsert: true }, function (err, result) {
+ callback(err, result && result.value ? result.value.data : null);
});
};
@@ -108,6 +108,7 @@ module.exports = function (db, module) {
if (!data) {
return callback(null, null);
}
+ delete data.expireAt;
var keys = Object.keys(data);
if (keys.length === 4 && data.hasOwnProperty('_key') && data.hasOwnProperty('score') && data.hasOwnProperty('value')) {
return callback(null, 'zset');
diff --git a/src/install.js b/src/install.js
index b55b1ed08e..2906adc9e8 100644
--- a/src/install.js
+++ b/src/install.js
@@ -353,6 +353,11 @@ function createGlobalModeratorsGroup(next) {
], next);
}
+function giveGlobalPrivileges(next) {
+ var privileges = require('./privileges');
+ privileges.global.give(['chat', 'upload:post:image'], 'registered-users', next);
+}
+
function createCategories(next) {
var Categories = require('./categories');
@@ -498,6 +503,7 @@ install.setup = function (callback) {
createCategories,
createAdministrator,
createGlobalModeratorsGroup,
+ giveGlobalPrivileges,
createMenuItems,
createWelcomePost,
enableDefaultPlugins,
diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js
index 3116c31a2b..f0220f2929 100644
--- a/src/messaging/notifications.js
+++ b/src/messaging/notifications.js
@@ -76,6 +76,7 @@ module.exports = function (Messaging) {
notifications.create({
type: 'new-chat',
+ subject: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
bodyLong: messageObj.content,
nid: 'chat_' + fromuid + '_' + roomId,
diff --git a/src/meta/build.js b/src/meta/build.js
index 2beb5f8af9..552c9aa55c 100644
--- a/src/meta/build.js
+++ b/src/meta/build.js
@@ -212,7 +212,7 @@ function build(targets, callback) {
}
winston.info('[build] Asset compilation successful. Completed in ' + totalTime + 'sec.');
- callback(null, true);
+ callback();
});
}
diff --git a/src/middleware/header.js b/src/middleware/header.js
index 5a896fcdd7..a0cf65d396 100644
--- a/src/middleware/header.js
+++ b/src/middleware/header.js
@@ -12,6 +12,7 @@ var meta = require('../meta');
var plugins = require('../plugins');
var navigation = require('../navigation');
var translator = require('../translator');
+var privileges = require('../privileges');
var utils = require('../utils');
var controllers = {
@@ -77,6 +78,9 @@ module.exports = function (middleware) {
isModerator: function (next) {
user.isModeratorOfAnyCategory(req.uid, next);
},
+ privileges: function (next) {
+ privileges.global.get(req.uid, next);
+ },
user: function (next) {
var userData = {
uid: 0,
@@ -132,6 +136,8 @@ module.exports = function (middleware) {
results.user.isAdmin = results.isAdmin;
results.user.isGlobalMod = results.isGlobalMod;
results.user.isMod = !!results.isModerator;
+ results.user.privileges = results.privileges;
+
results.user.uid = parseInt(results.user.uid, 10);
results.user.email = String(results.user.email);
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
@@ -183,6 +189,7 @@ module.exports = function (middleware) {
templateValues.isAdmin = results.user.isAdmin;
templateValues.isGlobalMod = results.user.isGlobalMod;
templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
+ templateValues.canChat = results.canChat && parseInt(meta.config.disableChat, 10) !== 1;
templateValues.user = results.user;
templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true });
templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS;
diff --git a/src/notifications.js b/src/notifications.js
index 6a7940d50d..4bf9782e61 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -220,7 +220,7 @@ function pushToUids(uids, notification, callback) {
async.eachLimit(uids, 3, function (uid, next) {
emailer.send('notification', uid, {
path: notification.path,
- subject: '[[notifications:new_notification_from, ' + meta.config.title + ']]',
+ subject: notification.subject || '[[notifications:new_notification_from, ' + meta.config.title + ']]',
intro: utils.stripHTMLTags(notification.bodyShort),
body: utils.stripHTMLTags(notification.bodyLong || ''),
showUnsubscribe: true,
diff --git a/src/plugins.js b/src/plugins.js
index 653edee5fe..f11ed63494 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -213,8 +213,8 @@ Plugins.list = function (matching, callback) {
require('request')(url, {
json: true,
}, function (err, res, body) {
- if (err) {
- winston.error('Error parsing plugins', err);
+ if (err || (res && res.statusCode !== 200)) {
+ winston.error('Error loading ' + url, err || body);
return Plugins.normalise([], callback);
}
@@ -225,7 +225,7 @@ Plugins.list = function (matching, callback) {
Plugins.normalise = function (apiReturn, callback) {
var pluginMap = {};
var dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
- apiReturn = apiReturn || [];
+ apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
for (var i = 0; i < apiReturn.length; i += 1) {
apiReturn[i].id = apiReturn[i].name;
apiReturn[i].installed = false;
diff --git a/src/privileges.js b/src/privileges.js
index c1ac018ec7..64d16d3e5c 100644
--- a/src/privileges.js
+++ b/src/privileges.js
@@ -12,8 +12,6 @@ privileges.privilegeLabels = [
{ name: 'Edit Posts' },
{ name: 'Delete Posts' },
{ name: 'Delete Topics' },
- { name: 'Upload Images' },
- { name: 'Upload Files' },
{ name: 'Purge' },
{ name: 'Moderate' },
];
@@ -28,8 +26,6 @@ privileges.userPrivilegeList = [
'posts:edit',
'posts:delete',
'topics:delete',
- 'upload:post:image',
- 'upload:post:file',
'purge',
'moderate',
];
@@ -40,6 +36,7 @@ privileges.groupPrivilegeList = privileges.userPrivilegeList.map(function (privi
privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList);
+require('./privileges/global')(privileges);
require('./privileges/categories')(privileges);
require('./privileges/topics')(privileges);
require('./privileges/posts')(privileges);
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index 69aec85135..60822c4e46 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -15,121 +15,20 @@ module.exports = function (privileges) {
privileges.categories.list = function (cid, callback) {
// Method used in admin/category controller to show all users/groups with privs in that given cid
- var privilegeLabels = privileges.privilegeLabels.slice();
- var userPrivilegeList = privileges.userPrivilegeList.slice();
- var groupPrivilegeList = privileges.groupPrivilegeList.slice();
async.waterfall([
function (next) {
async.parallel({
labels: function (next) {
async.parallel({
- users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privilegeLabels),
- groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privilegeLabels),
+ users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privileges.privilegeLabels.slice()),
+ groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privileges.privilegeLabels.slice()),
}, next);
},
users: function (next) {
- var userPrivileges;
- var memberSets;
- async.waterfall([
- async.apply(plugins.fireHook, 'filter:privileges.list', userPrivilegeList),
- function (_privs, next) {
- userPrivileges = _privs;
- groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
- return 'cid:' + cid + ':privileges:' + privilege;
- }), next);
- },
- function (_memberSets, next) {
- memberSets = _memberSets.map(function (set) {
- return set.map(function (uid) {
- return parseInt(uid, 10);
- });
- });
-
- var members = _.uniq(_.flatten(memberSets));
-
- user.getUsersFields(members, ['picture', 'username'], next);
- },
- function (memberData, next) {
- memberData.forEach(function (member) {
- member.privileges = {};
- for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
- member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
- }
- });
-
- next(null, memberData);
- },
- ], next);
+ helpers.getUserPrivileges(cid, 'filter:privileges.list', privileges.userPrivilegeList, next);
},
groups: function (next) {
- var groupPrivileges;
- async.waterfall([
- async.apply(plugins.fireHook, 'filter:privileges.groups.list', groupPrivilegeList),
- function (_privs, next) {
- groupPrivileges = _privs;
- async.parallel({
- memberSets: function (next) {
- groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
- return 'cid:' + cid + ':privileges:' + privilege;
- }), next);
- },
- groupNames: function (next) {
- groups.getGroups('groups:createtime', 0, -1, next);
- },
- }, next);
- },
- function (results, next) {
- var memberSets = results.memberSets;
- var uniqueGroups = _.uniq(_.flatten(memberSets));
-
- var groupNames = results.groupNames.filter(function (groupName) {
- return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
- });
-
- groupNames = groups.ephemeralGroups.concat(groupNames);
- var registeredUsersIndex = groupNames.indexOf('registered-users');
- if (registeredUsersIndex !== -1) {
- groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
- } else {
- groupNames = ['registered-users'].concat(groupNames);
- }
-
- var adminIndex = groupNames.indexOf('administrators');
- if (adminIndex !== -1) {
- groupNames.splice(adminIndex, 1);
- }
-
- var memberPrivs;
-
- var memberData = groupNames.map(function (member) {
- memberPrivs = {};
-
- for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
- memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
- }
- return {
- name: member,
- privileges: memberPrivs,
- };
- });
-
- next(null, memberData);
- },
- function (memberData, next) {
- // Grab privacy info for the groups as well
- async.map(memberData, function (member, next) {
- async.waterfall([
- function (next) {
- groups.isPrivate(member.name, next);
- },
- function (isPrivate, next) {
- member.isPrivate = isPrivate;
- next(null, member);
- },
- ], next);
- }, next);
- },
- ], next);
+ helpers.getGroupPrivileges(cid, 'filter:privileges.groups.list', privileges.groupPrivilegeList, next);
},
}, next);
},
@@ -299,19 +198,13 @@ module.exports = function (privileges) {
};
privileges.categories.give = function (privileges, cid, groupName, callback) {
- giveOrRescind(groups.join, privileges, cid, groupName, callback);
+ helpers.giveOrRescind(groups.join, privileges, cid, groupName, callback);
};
privileges.categories.rescind = function (privileges, cid, groupName, callback) {
- giveOrRescind(groups.leave, privileges, cid, groupName, callback);
+ helpers.giveOrRescind(groups.leave, privileges, cid, groupName, callback);
};
- function giveOrRescind(method, privileges, cid, groupName, callback) {
- async.eachSeries(privileges, function (privilege, next) {
- method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
- }, callback);
- }
-
privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) {
async.waterfall([
function (next) {
diff --git a/src/privileges/global.js b/src/privileges/global.js
new file mode 100644
index 0000000000..f1f88c4fff
--- /dev/null
+++ b/src/privileges/global.js
@@ -0,0 +1,128 @@
+
+'use strict';
+
+var async = require('async');
+var _ = require('lodash');
+
+var user = require('../user');
+var groups = require('../groups');
+var helpers = require('./helpers');
+var plugins = require('../plugins');
+
+module.exports = function (privileges) {
+ privileges.global = {};
+
+ privileges.global.privilegeLabels = [
+ { name: 'Chat' },
+ { name: 'Upload Images' },
+ { name: 'Upload Files' },
+ ];
+
+ privileges.global.userPrivilegeList = [
+ 'chat',
+ 'upload:post:image',
+ 'upload:post:file',
+ ];
+
+ privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) {
+ return 'groups:' + privilege;
+ });
+
+ privileges.global.list = function (callback) {
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ labels: function (next) {
+ async.parallel({
+ users: async.apply(plugins.fireHook, 'filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()),
+ groups: async.apply(plugins.fireHook, 'filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()),
+ }, next);
+ },
+ users: function (next) {
+ helpers.getUserPrivileges(0, 'filter:privileges.global.list', privileges.global.userPrivilegeList, next);
+ },
+ groups: function (next) {
+ helpers.getGroupPrivileges(0, 'filter:privileges.global.groups.list', privileges.global.groupPrivilegeList, next);
+ },
+ }, next);
+ },
+ function (payload, next) {
+ // This is a hack because I can't do {labels.users.length} to echo the count in templates.js
+ payload.columnCount = payload.labels.users.length + 2;
+ next(null, payload);
+ },
+ ], callback);
+ };
+
+ privileges.global.get = function (uid, callback) {
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ privileges: function (next) {
+ helpers.isUserAllowedTo(privileges.global.userPrivilegeList, uid, 0, next);
+ },
+ isAdministrator: function (next) {
+ user.isAdministrator(uid, next);
+ },
+ isGlobalModerator: function (next) {
+ user.isGlobalModerator(uid, next);
+ },
+ }, next);
+ },
+ function (results, next) {
+ var privData = _.zipObject(privileges.global.userPrivilegeList, results.privileges);
+ var isAdminOrMod = results.isAdministrator || results.isGlobalModerator;
+
+ plugins.fireHook('filter:privileges.global.get', {
+ chat: privData.chat || isAdminOrMod,
+ 'upload:post:image': privData['upload:post:image'] || isAdminOrMod,
+ 'upload:post:file': privData['upload:post:file'] || isAdminOrMod,
+ }, next);
+ },
+ ], callback);
+ };
+
+ privileges.global.can = function (privilege, uid, callback) {
+ helpers.some([
+ function (next) {
+ helpers.isUserAllowedTo(privilege, uid, [0], function (err, results) {
+ next(err, Array.isArray(results) && results.length ? results[0] : false);
+ });
+ },
+ function (next) {
+ user.isGlobalModerator(uid, next);
+ },
+ function (next) {
+ user.isAdministrator(uid, next);
+ },
+ ], callback);
+ };
+
+ privileges.global.give = function (privileges, groupName, callback) {
+ helpers.giveOrRescind(groups.join, privileges, 0, groupName, callback);
+ };
+
+ privileges.global.rescind = function (privileges, groupName, callback) {
+ helpers.giveOrRescind(groups.leave, privileges, 0, groupName, callback);
+ };
+
+ privileges.global.userPrivileges = function (uid, callback) {
+ var tasks = {};
+
+ privileges.global.userPrivilegeList.forEach(function (privilege) {
+ tasks[privilege] = async.apply(groups.isMember, uid, 'cid:0:privileges:' + privilege);
+ });
+
+ async.parallel(tasks, callback);
+ };
+
+ privileges.global.groupPrivileges = function (groupName, callback) {
+ var tasks = {};
+
+ privileges.global.groupPrivilegeList.forEach(function (privilege) {
+ tasks[privilege] = async.apply(groups.isMember, groupName, 'cid:0:privileges:' + privilege);
+ });
+
+ async.parallel(tasks, callback);
+ };
+};
diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js
index 0f56e4f9c8..c3452c495e 100644
--- a/src/privileges/helpers.js
+++ b/src/privileges/helpers.js
@@ -2,7 +2,11 @@
'use strict';
var async = require('async');
+var _ = require('lodash');
+
var groups = require('../groups');
+var user = require('../user');
+var plugins = require('../plugins');
var helpers = module.exports;
@@ -111,3 +115,115 @@ function isGuestAllowedToPrivileges(privileges, cid, callback) {
groups.isMemberOfGroups('guests', groupKeys, callback);
}
+
+helpers.getUserPrivileges = function (cid, hookName, userPrivilegeList, callback) {
+ var userPrivileges;
+ var memberSets;
+ async.waterfall([
+ async.apply(plugins.fireHook, hookName, userPrivilegeList.slice()),
+ function (_privs, next) {
+ userPrivileges = _privs;
+ groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
+ return 'cid:' + cid + ':privileges:' + privilege;
+ }), next);
+ },
+ function (_memberSets, next) {
+ memberSets = _memberSets.map(function (set) {
+ return set.map(function (uid) {
+ return parseInt(uid, 10);
+ });
+ });
+
+ var members = _.uniq(_.flatten(memberSets));
+
+ user.getUsersFields(members, ['picture', 'username'], next);
+ },
+ function (memberData, next) {
+ memberData.forEach(function (member) {
+ member.privileges = {};
+ for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
+ member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
+ }
+ });
+
+ next(null, memberData);
+ },
+ ], callback);
+};
+
+helpers.getGroupPrivileges = function (cid, hookName, groupPrivilegeList, callback) {
+ var groupPrivileges;
+ async.waterfall([
+ async.apply(plugins.fireHook, hookName, groupPrivilegeList.slice()),
+ function (_privs, next) {
+ groupPrivileges = _privs;
+ async.parallel({
+ memberSets: function (next) {
+ groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
+ return 'cid:' + cid + ':privileges:' + privilege;
+ }), next);
+ },
+ groupNames: function (next) {
+ groups.getGroups('groups:createtime', 0, -1, next);
+ },
+ }, next);
+ },
+ function (results, next) {
+ var memberSets = results.memberSets;
+ var uniqueGroups = _.uniq(_.flatten(memberSets));
+
+ var groupNames = results.groupNames.filter(function (groupName) {
+ return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
+ });
+
+ groupNames = groups.ephemeralGroups.concat(groupNames);
+ var registeredUsersIndex = groupNames.indexOf('registered-users');
+ if (registeredUsersIndex !== -1) {
+ groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
+ } else {
+ groupNames = ['registered-users'].concat(groupNames);
+ }
+
+ var adminIndex = groupNames.indexOf('administrators');
+ if (adminIndex !== -1) {
+ groupNames.splice(adminIndex, 1);
+ }
+
+ var memberPrivs;
+
+ var memberData = groupNames.map(function (member) {
+ memberPrivs = {};
+
+ for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
+ memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
+ }
+ return {
+ name: member,
+ privileges: memberPrivs,
+ };
+ });
+
+ next(null, memberData);
+ },
+ function (memberData, next) {
+ // Grab privacy info for the groups as well
+ async.map(memberData, function (member, next) {
+ async.waterfall([
+ function (next) {
+ groups.isPrivate(member.name, next);
+ },
+ function (isPrivate, next) {
+ member.isPrivate = isPrivate;
+ next(null, member);
+ },
+ ], next);
+ }, next);
+ },
+ ], callback);
+};
+
+helpers.giveOrRescind = function (method, privileges, cid, groupName, callback) {
+ async.eachSeries(privileges, function (privilege, next) {
+ method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
+ }, callback);
+};
diff --git a/src/routes/admin.js b/src/routes/admin.js
index ba4048516e..db0ce7798c 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -55,6 +55,7 @@ function addRoutes(router, middleware, controllers) {
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
+ router.get('/manage/privileges/:cid?', middlewares, controllers.admin.privileges.get);
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
router.get('/manage/post-queue', middlewares, controllers.admin.postQueue.get);
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
@@ -71,6 +72,8 @@ function addRoutes(router, middleware, controllers) {
router.get('/manage/users/banned', middlewares, controllers.admin.users.banned);
router.get('/manage/registration', middlewares, controllers.admin.users.registrationQueue);
+ router.get('/manage/admins-mods', middlewares, controllers.admin.adminsMods.get);
+
router.get('/manage/groups', middlewares, controllers.admin.groups.list);
router.get('/manage/groups/:name', middlewares, controllers.admin.groups.get);
diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js
index 232b2041d1..7bd491c8cd 100644
--- a/src/socket.io/admin/categories.js
+++ b/src/socket.io/admin/categories.js
@@ -83,7 +83,11 @@ Categories.setPrivilege = function (socket, data, callback) {
};
Categories.getPrivilegeSettings = function (socket, cid, callback) {
- privileges.categories.list(cid, callback);
+ if (!parseInt(cid, 10)) {
+ privileges.global.list(callback);
+ } else {
+ privileges.categories.list(cid, callback);
+ }
};
Categories.copyPrivilegesToChildren = function (socket, cid, callback) {
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index 7ff5accf40..a0328ab82d 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -11,6 +11,7 @@ var Messaging = require('../messaging');
var utils = require('../utils');
var server = require('./');
var user = require('../user');
+var privileges = require('../privileges');
var SocketModules = module.exports;
@@ -73,6 +74,12 @@ SocketModules.chats.newRoom = function (socket, data, callback) {
async.waterfall([
function (next) {
+ privileges.global.can('chat', socket.uid, next);
+ },
+ function (canChat, next) {
+ if (!canChat) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
Messaging.canMessageUser(socket.uid, data.touid, next);
},
function (next) {
@@ -92,6 +99,13 @@ SocketModules.chats.send = function (socket, data, callback) {
async.waterfall([
function (next) {
+ privileges.global.can('chat', socket.uid, next);
+ },
+ function (canChat, next) {
+ if (!canChat) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
+
plugins.fireHook('filter:messaging.send', {
data: data,
uid: socket.uid,
@@ -133,6 +147,13 @@ SocketModules.chats.loadRoom = function (socket, data, callback) {
async.waterfall([
function (next) {
+ privileges.global.can('chat', socket.uid, next);
+ },
+ function (canChat, next) {
+ if (!canChat) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
+
Messaging.isUserInRoom(socket.uid, data.roomId, next);
},
function (inRoom, next) {
@@ -174,6 +195,13 @@ SocketModules.chats.addUserToRoom = function (socket, data, callback) {
var uid;
async.waterfall([
function (next) {
+ privileges.global.can('chat', socket.uid, next);
+ },
+ function (canChat, next) {
+ if (!canChat) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
+
Messaging.getUserCountInRoom(data.roomId, next);
},
function (userCount, next) {
diff --git a/src/topics/follow.js b/src/topics/follow.js
index cf8754bcc5..a590ad2392 100644
--- a/src/topics/follow.js
+++ b/src/topics/follow.js
@@ -219,6 +219,7 @@ module.exports = function (Topics) {
notifications.create({
type: 'new-reply',
+ subject: title,
bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
bodyLong: postData.content,
pid: postData.pid,
diff --git a/src/upgrades/1.8.0/chat_privilege.js b/src/upgrades/1.8.0/chat_privilege.js
new file mode 100644
index 0000000000..c4bd2ff8d1
--- /dev/null
+++ b/src/upgrades/1.8.0/chat_privilege.js
@@ -0,0 +1,12 @@
+'use strict';
+
+
+var groups = require('../../groups');
+
+module.exports = {
+ name: 'Give chat privilege to registered-users',
+ timestamp: Date.UTC(2017, 11, 18),
+ method: function (callback) {
+ groups.join('cid:0:privileges:groups:chat', 'registered-users', callback);
+ },
+};
diff --git a/src/upgrades/1.8.0/global_upload_privilege.js b/src/upgrades/1.8.0/global_upload_privilege.js
new file mode 100644
index 0000000000..22473a9ee0
--- /dev/null
+++ b/src/upgrades/1.8.0/global_upload_privilege.js
@@ -0,0 +1,45 @@
+'use strict';
+
+
+var async = require('async');
+var groups = require('../../groups');
+var privileges = require('../../privileges');
+var db = require('../../database');
+
+module.exports = {
+ name: 'Give upload privilege to registered-users globally if it is given on a category',
+ timestamp: Date.UTC(2018, 0, 3),
+ method: function (callback) {
+ db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
+ if (err) {
+ return callback(err);
+ }
+ async.eachSeries(cids, function (cid, next) {
+ getGroupPrivileges(cid, function (err, groupPrivileges) {
+ if (err) {
+ return next(err);
+ }
+
+ var privs = [];
+ if (groupPrivileges['groups:upload:post:image']) {
+ privs.push('upload:post:image');
+ }
+ if (groupPrivileges['groups:upload:post:file']) {
+ privs.push('upload:post:file');
+ }
+ privileges.global.give(privs, 'registered-users', next);
+ });
+ }, callback);
+ });
+ },
+};
+
+function getGroupPrivileges(cid, callback) {
+ var tasks = {};
+
+ ['groups:upload:post:image', 'groups:upload:post:file'].forEach(function (privilege) {
+ tasks[privilege] = async.apply(groups.isMember, 'registered-users', 'cid:' + cid + ':privileges:' + privilege);
+ });
+
+ async.parallel(tasks, callback);
+}
diff --git a/src/views/admin/manage/admins-mods.tpl b/src/views/admin/manage/admins-mods.tpl
new file mode 100644
index 0000000000..76d3982302
--- /dev/null
+++ b/src/views/admin/manage/admins-mods.tpl
@@ -0,0 +1,64 @@
+