mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 19:46:01 +01:00
Merge branch 'develop' into chat-rewrite
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 <code><head></code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-header\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",
|
||||
"custom-header.description": "Zde zadejte uživatelské HTML (mimo Meta Tags, atd.), které bude připojeno k části značek <code><head></code> vašeho fóra.. Značky pro „script” jsou povoleny, ale nedoporučujeme je, neboť je dostupný <a href=\"#custom-header\" data-toggle=\"tab\">Uživatelský Javascript</a> .",
|
||||
"custom-header.enable": "Povolit uživatelskou hlavičku",
|
||||
|
||||
"custom-css.livereload": "Povolit aktuální znovu načtení",
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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 <strong></strong>",
|
||||
"search.keep-typing": "Pište dále pro zobrazení výsledků…",
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
10
public/language/en-GB/admin/manage/admins-mods.json
Normal file
10
public/language/en-GB/admin/manage/admins-mods.json
Normal file
@@ -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"
|
||||
}
|
||||
4
public/language/en-GB/admin/manage/privileges.json
Normal file
4
public/language/en-GB/admin/manage/privileges.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"global": "Global",
|
||||
"global.no-users": "No user-specific global privileges."
|
||||
}
|
||||
@@ -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)?",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <code><head></code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-header\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
@@ -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!"
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -104,6 +104,6 @@
|
||||
"cookies.accept": "Ho capito!",
|
||||
"cookies.learn_more": "Scopri di più",
|
||||
"edited": "Modificato",
|
||||
"disabled": "Disabled",
|
||||
"select": "Select"
|
||||
"disabled": "Disabilitato",
|
||||
"select": "Seleziona"
|
||||
}
|
||||
@@ -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?"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"user_flagged_post_in": "<strong>%1</strong> ha segnalato un post in <strong>%2</strong>",
|
||||
"user_flagged_post_in_dual": "<strong>%1</strong> e <strong>%2</strong> hanno segnalato un post in <strong>%3</strong>",
|
||||
"user_flagged_post_in_multiple": "<strong>%1</strong> ed altri %2 hanno segnalato un post in <strong>%3</strong>",
|
||||
"user_flagged_user": "<strong>%1</strong> flagged a user profile (%2)",
|
||||
"user_flagged_user_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a user profile (%3)",
|
||||
"user_flagged_user_multiple": "<strong>%1</strong> and %2 others flagged a user profile (%3)",
|
||||
"user_flagged_user": "<strong>%1</strong> ha segnalato un utente (%2)",
|
||||
"user_flagged_user_dual": "<strong>%1</strong> e <strong>%2</strong> hanno segnalato un utente (%3)",
|
||||
"user_flagged_user_multiple": "<strong>%1</strong> e altri %2 hanno segnalato un utente (%3)",
|
||||
"user_posted_to": "<strong>%1</strong> ha postato una risposta a: <strong>%2</strong>",
|
||||
"user_posted_to_dual": "<strong>%1</strong> e <strong>%2</strong> hanno postato una risposta su: <strong>%3</strong>",
|
||||
"user_posted_to_multiple": "<strong>%1</strong> ed altri %2 hanno postato una risposta su: <strong>%3</strong>",
|
||||
@@ -42,23 +42,23 @@
|
||||
"new_register": "<strong>%1</strong> ha inviato una richiesta di registrazione.",
|
||||
"new_register_multiple": "Ci sono <strong>%1</strong> richieste di registrazione che attendono di essere esaminate.",
|
||||
"flag_assigned_to_you": "<strong>Flag %1</strong> 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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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 @<span id='yourUsername'>username</span>.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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 è <strong>%1</strong>",
|
||||
"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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
27
public/less/admin/manage/admins-mods.less
Normal file
27
public/less/admin/manage/admins-mods.less
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
142
public/src/admin/manage/admins-mods.js
Normal file
142
public/src/admin/manage/admins-mods.js
Normal file
@@ -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;
|
||||
});
|
||||
@@ -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('<i class="fa fa-spin fa-spinner"></i>');
|
||||
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: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.user-search]]" />',
|
||||
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: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.group-search]]" />',
|
||||
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;
|
||||
});
|
||||
|
||||
194
public/src/admin/manage/privileges.js
Normal file
194
public/src/admin/manage/privileges.js
Normal file
@@ -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('<i class="fa fa-spin fa-spinner"></i>');
|
||||
Privileges.refreshPrivilegeTable();
|
||||
});
|
||||
};
|
||||
|
||||
Privileges.addUserToPrivilegeTable = function () {
|
||||
var modal = bootbox.dialog({
|
||||
title: '[[admin/manage/categories:alert.find-user]]',
|
||||
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.user-search]]" />',
|
||||
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: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.group-search]]" />',
|
||||
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;
|
||||
});
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -379,7 +379,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,
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -62,7 +62,6 @@ module.exports = function (Categories) {
|
||||
'posts:edit',
|
||||
'posts:delete',
|
||||
'topics:delete',
|
||||
'upload:post:image',
|
||||
];
|
||||
|
||||
async.series([
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'),
|
||||
|
||||
50
src/controllers/admin/admins-mods.js
Normal file
50
src/controllers/admin/admins-mods.js
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
39
src/controllers/admin/privileges.js
Normal file
39
src/controllers/admin/privileges.js
Normal file
@@ -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);
|
||||
};
|
||||
@@ -1,51 +1,45 @@
|
||||
'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 adminHomePageRoute() {
|
||||
return (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
|
||||
}
|
||||
|
||||
function getRouteDisableUserHomePage(uid, next) {
|
||||
next(null, adminHomePageRoute);
|
||||
}
|
||||
|
||||
function getRouteAllowUserHomePage(uid, next) {
|
||||
user.getSettings(uid, function (err, settings) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var route = adminHomePageRoute;
|
||||
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);
|
||||
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)) {
|
||||
@@ -55,7 +49,8 @@ function rewrite(req, res, next) {
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
], next);
|
||||
}
|
||||
|
||||
exports.rewrite = rewrite;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -212,7 +212,7 @@ function build(targets, callback) {
|
||||
}
|
||||
|
||||
winston.info('[build] Asset compilation successful. Completed in ' + totalTime + 'sec.');
|
||||
callback(null, true);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
128
src/privileges/global.js
Normal file
128
src/privileges/global.js
Normal file
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -83,7 +83,11 @@ Categories.setPrivilege = function (socket, data, callback) {
|
||||
};
|
||||
|
||||
Categories.getPrivilegeSettings = function (socket, cid, callback) {
|
||||
if (!parseInt(cid, 10)) {
|
||||
privileges.global.list(callback);
|
||||
} else {
|
||||
privileges.categories.list(cid, callback);
|
||||
}
|
||||
};
|
||||
|
||||
Categories.copyPrivilegesToChildren = function (socket, cid, callback) {
|
||||
|
||||
@@ -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) {
|
||||
@@ -182,6 +203,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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
12
src/upgrades/1.8.0/chat_privilege.js
Normal file
12
src/upgrades/1.8.0/chat_privilege.js
Normal file
@@ -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);
|
||||
},
|
||||
};
|
||||
45
src/upgrades/1.8.0/global_upload_privilege.js
Normal file
45
src/upgrades/1.8.0/global_upload_privilege.js
Normal file
@@ -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);
|
||||
}
|
||||
64
src/views/admin/manage/admins-mods.tpl
Normal file
64
src/views/admin/manage/admins-mods.tpl
Normal file
@@ -0,0 +1,64 @@
|
||||
<div class="admins-mods">
|
||||
<h4><!-- IF admins.icon --><i class="fa {admins.icon}"></i> <!-- ENDIF admins.icon -->[[admin/manage/admins-mods:administrators]]</h4>
|
||||
<div class="administrator-area">
|
||||
<!-- BEGIN admins.members -->
|
||||
<div class="user-card pull-left" data-uid="{admins.members.uid}">
|
||||
<!-- IF admins.members.picture -->
|
||||
<img class="avatar avatar-sm" src="{admins.members.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {admins.members.icon:bgColor};">{admins.members.icon:text}</div>
|
||||
<!-- ENDIF admins.members.picture -->
|
||||
<a href="{config.relative_path}/user/{admins.members.userslug}">{admins.members.username}</a>
|
||||
<i class="remove-user-icon fa fa-times" role="button"></i>
|
||||
</div>
|
||||
<!-- END admins.members -->
|
||||
</div>
|
||||
<input id="admin-search" class="form-control" placeholder="[[admin/manage/admins-mods:add-administrator]]" />
|
||||
|
||||
<br/>
|
||||
|
||||
<h4><!-- IF globalMods.icon --><i class="fa {globalMods.icon}"></i> <!-- ENDIF globalMods.icon -->[[admin/manage/admins-mods:global-moderators]]</h4>
|
||||
<div class="global-moderator-area">
|
||||
<!-- BEGIN globalMods.members -->
|
||||
<div class="user-card pull-left" data-uid="{globalMods.members.uid}">
|
||||
<!-- IF globalMods.members.picture -->
|
||||
<img class="avatar avatar-sm" src="{globalMods.members.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {globalMods.members.icon:bgColor};">{globalMods.members.icon:text}</div>
|
||||
<!-- ENDIF globalMods.members.picture -->
|
||||
<a href="{config.relative_path}/user/{globalMods.members.userslug}">{globalMods.members.username}</a>
|
||||
<i class="remove-user-icon fa fa-times" role="button"></i>
|
||||
</div>
|
||||
<!-- END globalMods.members -->
|
||||
</div>
|
||||
|
||||
<div id="no-global-mods-warning" class="<!-- IF globalMods.members.length -->hidden<!-- ENDIF globalMods.members.length -->">[[admin/manage/admins-mods:no-global-moderators]]</div>
|
||||
|
||||
<input id="global-mod-search" class="form-control" placeholder="[[admin/manage/admins-mods:add-global-moderator]]" />
|
||||
|
||||
<br/>
|
||||
|
||||
<!-- BEGIN categories -->
|
||||
<div class="categories category-wrapper category-depth-{categories.depth}">
|
||||
<h4><!-- IF categories.icon --><i class="fa {categories.icon}"></i> <!-- ENDIF categories.icon -->[[admin/manage/admins-mods:moderators-of-category, {categories.name}]]</h4>
|
||||
<div class="moderator-area" data-cid="{categories.cid}">
|
||||
<!-- BEGIN categories.moderators -->
|
||||
<div class="user-card pull-left" data-uid="{categories.moderators.uid}">
|
||||
<!-- IF categories.moderators.picture -->
|
||||
<img class="avatar avatar-sm" src="{categories.moderators.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {categories.moderators.icon:bgColor};">{categories.moderators.icon:text}</div>
|
||||
<!-- ENDIF categories.moderators.picture -->
|
||||
<a href="{config.relative_path}/user/{categories.moderators.userslug}">{categories.moderators.username}</a>
|
||||
<i class="remove-user-icon fa fa-times" role="button"></i>
|
||||
</div>
|
||||
<!-- END categories.moderators -->
|
||||
</div>
|
||||
|
||||
<div data-cid="{categories.cid}" class="no-moderator-warning <!-- IF categories.moderators.length -->hidden<!-- ENDIF categories.moderators.length -->">[[admin/manage/admins-mods:no-moderators]]</div>
|
||||
|
||||
<input data-cid="{categories.cid}" class="form-control moderator-search" placeholder="[[admin/manage/admins-mods:add-moderator]]" />
|
||||
</div>
|
||||
<br/>
|
||||
<!-- END categories -->
|
||||
</div>
|
||||
@@ -2,15 +2,7 @@
|
||||
|
||||
<form role="form" class="category" data-cid="{category.cid}">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="active"><a href="#category-settings" data-toggle="tab">
|
||||
[[admin/manage/categories:settings]]
|
||||
</a></li>
|
||||
<li><a href="#privileges" data-toggle="tab">[[admin/manage/categories:privileges]]</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 pull-right">
|
||||
<select id="category-selector" class="form-control">
|
||||
<!-- BEGIN allCategories -->
|
||||
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
|
||||
@@ -174,19 +166,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade col-xs-12" id="privileges">
|
||||
<p>
|
||||
[[admin/manage/categories:privileges.description]]
|
||||
</p>
|
||||
<p class="text-warning">
|
||||
[[admin/manage/categories:privileges.warning]]
|
||||
</p>
|
||||
<hr />
|
||||
<div class="privilege-table-container">
|
||||
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
31
src/views/admin/manage/privileges.tpl
Normal file
31
src/views/admin/manage/privileges.tpl
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="row">
|
||||
<form role="form" class="category">
|
||||
<div class="row">
|
||||
<div class="col-md-3 pull-right">
|
||||
<select id="category-selector" class="form-control">
|
||||
<option value="global" <!-- IF !cid --> selected <!-- ENDIF !cid -->>[[admin/manage/privileges:global]]</option>
|
||||
<option disabled>_____________</option>
|
||||
<!-- BEGIN allCategories -->
|
||||
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
|
||||
<!-- END allCategories -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="">
|
||||
<p>
|
||||
[[admin/manage/categories:privileges.description]]
|
||||
</p>
|
||||
<hr />
|
||||
<div class="privilege-table-container">
|
||||
<!-- IF cid -->
|
||||
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
||||
<!-- ELSE -->
|
||||
<!-- IMPORT admin/partials/global/privileges.tpl -->
|
||||
<!-- ENDIF cid -->
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -8,9 +8,6 @@
|
||||
<div class="btn-group pull-right">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[admin/manage/users:edit]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" class="admin-user"><i class="fa fa-fw fa-shield"></i> [[admin/manage/users:make-admin]]</a></li>
|
||||
<li><a href="#" class="remove-admin-user"><i class="fa fa-fw fa-ban"></i> [[admin/manage/users:remove-admin]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="validate-email"><i class="fa fa-fw fa-check"></i> [[admin/manage/users:validate-email]]</a></li>
|
||||
<li><a href="#" class="send-validation-email"><i class="fa fa-fw fa-mail-forward"></i> [[admin/manage/users:send-validation-email]]</a></li>
|
||||
<li><a href="#" class="password-reset-email"><i class="fa fa-fw fa-key"></i> [[admin/manage/users:password-reset-email]]</a></li>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<th class="arrowed" colspan="3">
|
||||
[[admin/manage/categories:privileges.section-viewing]]
|
||||
</th>
|
||||
<th class="arrowed" colspan="8">
|
||||
<th class="arrowed" colspan="6">
|
||||
[[admin/manage/categories:privileges.section-posting]]
|
||||
</th>
|
||||
<th class="arrowed" colspan="2">
|
||||
@@ -61,7 +61,7 @@
|
||||
<th class="arrowed" colspan="3">
|
||||
[[admin/manage/categories:privileges.section-viewing]]
|
||||
</th>
|
||||
<th class="arrowed" colspan="8">
|
||||
<th class="arrowed" colspan="6">
|
||||
[[admin/manage/categories:privileges.section-posting]]
|
||||
</th>
|
||||
<th class="arrowed" colspan="2">
|
||||
|
||||
86
src/views/admin/partials/global/privileges.tpl
Normal file
86
src/views/admin/partials/global/privileges.tpl
Normal file
@@ -0,0 +1,86 @@
|
||||
<table class="table table-striped privilege-table">
|
||||
<thead>
|
||||
<tr class="privilege-table-header">
|
||||
<th colspan="3"></th>
|
||||
</tr><tr><!-- zebrastripe reset --></tr>
|
||||
<tr>
|
||||
<th colspan="2">[[admin/manage/categories:privileges.section-user]]</th>
|
||||
<!-- BEGIN privileges.labels.users -->
|
||||
<th class="text-center">{privileges.labels.users.name}</th>
|
||||
<!-- END privileges.labels.users -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- IF privileges.users.length -->
|
||||
<!-- BEGIN privileges.users -->
|
||||
<tr data-uid="{privileges.users.uid}">
|
||||
<td>
|
||||
<!-- IF ../picture -->
|
||||
<img class="avatar avatar-sm" src="{privileges.users.picture}" title="{privileges.users.username}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {../icon:bgColor};">{../icon:text}</div>
|
||||
<!-- ENDIF ../picture -->
|
||||
</td>
|
||||
<td>{privileges.users.username}</td>
|
||||
{function.spawnPrivilegeStates, privileges.users.username, ../privileges}
|
||||
</tr>
|
||||
<!-- END privileges.users -->
|
||||
<tr>
|
||||
<td colspan="{privileges.columnCount}">
|
||||
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
|
||||
[[admin/manage/categories:privileges.search-user]]
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ELSE -->
|
||||
<tr>
|
||||
<td colspan="{privileges.columnCount}">
|
||||
[[admin/manage/privileges:global.no-users]]
|
||||
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
|
||||
[[admin/manage/categories:privileges.search-user]]
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ENDIF privileges.users.length -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="table table-striped privilege-table">
|
||||
<thead>
|
||||
<tr class="privilege-table-header">
|
||||
<th colspan="3"></th>
|
||||
</tr><tr><!-- zebrastripe reset --></tr>
|
||||
<tr>
|
||||
<th colspan="2">[[admin/manage/categories:privileges.section-group]]</th>
|
||||
<!-- BEGIN privileges.labels.groups -->
|
||||
<th class="text-center">{privileges.labels.groups.name}</th>
|
||||
<!-- END privileges.labels.groups -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- BEGIN privileges.groups -->
|
||||
<tr data-group-name="{privileges.groups.name}" data-private="<!-- IF privileges.groups.isPrivate -->1<!-- ELSE -->0<!-- ENDIF privileges.groups.isPrivate -->">
|
||||
<td>
|
||||
<!-- IF privileges.groups.isPrivate -->
|
||||
<i class="fa fa-lock text-muted" title="[[admin/manage/categories:privileges.group-private]]"></i>
|
||||
<!-- ENDIF privileges.groups.isPrivate -->
|
||||
{privileges.groups.name}
|
||||
</td>
|
||||
<td></td>
|
||||
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
|
||||
</tr>
|
||||
<!-- END privileges.groups -->
|
||||
<tr>
|
||||
<td colspan="{privileges.columnCount}">
|
||||
<div class="btn-toolbar">
|
||||
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.group">
|
||||
[[admin/manage/categories:privileges.search-group]]
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="help-block">
|
||||
[[admin/manage/categories:privileges.inherit]]
|
||||
</div>
|
||||
@@ -15,7 +15,9 @@
|
||||
<h3 class="menu-section-title">[[admin/menu:section-manage]]</h3>
|
||||
<ul class="menu-section-list">
|
||||
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/admins-mods">[[admin/menu:manage/admins-mods]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
||||
@@ -188,7 +190,9 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">[[admin/menu:section-manage]]</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/admins-mods">[[admin/menu:manage/admins-mods]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
||||
|
||||
@@ -638,7 +638,7 @@ describe('Categories', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should load user privileges', function (done) {
|
||||
it('should load category user privileges', function (done) {
|
||||
privileges.categories.userPrivileges(categoryObj.cid, 1, function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(data, {
|
||||
@@ -651,8 +651,6 @@ describe('Categories', function () {
|
||||
'topics:tag': false,
|
||||
'topics:delete': false,
|
||||
'posts:edit': false,
|
||||
'upload:post:file': false,
|
||||
'upload:post:image': false,
|
||||
purge: false,
|
||||
moderate: false,
|
||||
});
|
||||
@@ -661,7 +659,20 @@ describe('Categories', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should load group privileges', function (done) {
|
||||
it('should load global user privileges', function (done) {
|
||||
privileges.global.userPrivileges(1, function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(data, {
|
||||
chat: false,
|
||||
'upload:post:image': false,
|
||||
'upload:post:file': false,
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load category group privileges', function (done) {
|
||||
privileges.categories.groupPrivileges(categoryObj.cid, 'registered-users', function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(data, {
|
||||
@@ -674,8 +685,6 @@ describe('Categories', function () {
|
||||
'groups:posts:delete': true,
|
||||
'groups:read': true,
|
||||
'groups:topics:read': true,
|
||||
'groups:upload:post:file': false,
|
||||
'groups:upload:post:image': true,
|
||||
'groups:purge': false,
|
||||
'groups:moderate': false,
|
||||
});
|
||||
@@ -684,6 +693,19 @@ describe('Categories', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should load global group privileges', function (done) {
|
||||
privileges.global.groupPrivileges('registered-users', function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(data, {
|
||||
'groups:chat': true,
|
||||
'groups:upload:post:image': true,
|
||||
'groups:upload:post:file': false,
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false if cid is falsy', function (done) {
|
||||
privileges.categories.isUserAllowedTo('find', null, adminUid, function (err, isAllowed) {
|
||||
assert.ifError(err);
|
||||
|
||||
@@ -255,6 +255,14 @@ describe('Admin Controllers', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/admins-mods', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/admins-mods', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 403 if no referer', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/users/csv', { jar: jar }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
|
||||
@@ -149,6 +149,21 @@ describe('Key methods', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should set then increment a key', function (done) {
|
||||
db.set('myIncrement', 1, function (err) {
|
||||
assert.ifError(err);
|
||||
db.increment('myIncrement', function (err, value) {
|
||||
assert.ifError(err);
|
||||
assert.equal(value, 2);
|
||||
db.get('myIncrement', function (err, value) {
|
||||
assert.ifError(err);
|
||||
assert.equal(value, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('rename', function () {
|
||||
|
||||
@@ -71,9 +71,9 @@ describe('Groups', function () {
|
||||
|
||||
describe('.list()', function () {
|
||||
it('should list the groups present', function (done) {
|
||||
Groups.getGroupsFromSet('groups:createtime', 0, 0, -1, function (err, groups) {
|
||||
Groups.getGroupsFromSet('groups:visible:createtime', 0, 0, -1, function (err, groups) {
|
||||
assert.ifError(err);
|
||||
assert.equal(groups.length, 7);
|
||||
assert.equal(groups.length, 4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -414,7 +414,7 @@ describe('Messaging Library', function () {
|
||||
|
||||
it('should fail to load room if user is not in', function (done) {
|
||||
socketModules.chats.loadRoom({ uid: 0 }, { roomId: roomId }, function (err) {
|
||||
assert.equal(err.message, '[[error:not-allowed]]');
|
||||
assert.equal(err.message, '[[error:no-privileges]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -629,11 +629,12 @@ describe('Messaging Library', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should 404 for guest', function (done) {
|
||||
it('should 500 for guest with no privilege error', function (done) {
|
||||
meta.config.disableChat = 0;
|
||||
request(nconf.get('url') + '/user/baz/chats', function (err, response) {
|
||||
request(nconf.get('url') + '/api/user/baz/chats', { json: true }, function (err, response, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert.equal(response.statusCode, 500);
|
||||
assert.equal(body.error, '[[error:no-privileges]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -154,6 +154,9 @@ function setupMockDefaults(callback) {
|
||||
winston.info('test_database flushed');
|
||||
setupDefaultConfigs(meta, next);
|
||||
},
|
||||
function (next) {
|
||||
giveDefaultGlobalPrivileges(next);
|
||||
},
|
||||
function (next) {
|
||||
meta.configs.init(next);
|
||||
},
|
||||
@@ -182,6 +185,11 @@ function setupDefaultConfigs(meta, next) {
|
||||
meta.configs.setOnEmpty(defaults, next);
|
||||
}
|
||||
|
||||
function giveDefaultGlobalPrivileges(next) {
|
||||
var privileges = require('../../src/privileges');
|
||||
privileges.global.give(['chat', 'upload:post:image'], 'registered-users', next);
|
||||
}
|
||||
|
||||
function enableDefaultPlugins(callback) {
|
||||
winston.info('Enabling default plugins\n');
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('Upload Controllers', function () {
|
||||
assert.ifError(err);
|
||||
jar = _jar;
|
||||
csrf_token = _csrf_token;
|
||||
privileges.categories.give(['upload:post:file'], cid, 'registered-users', done);
|
||||
privileges.global.give(['upload:post:file'], 'registered-users', done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,17 +77,8 @@ describe('Upload Controllers', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to upload an image to a post with invalid cid', function (done) {
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: '0' }, jar, csrf_token, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 500);
|
||||
assert.equal(body.error, '[[error:category-not-selected]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should upload an image to a post', function (done) {
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: cid }, jar, csrf_token, function (err, res, body) {
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(Array.isArray(body));
|
||||
@@ -100,7 +91,7 @@ describe('Upload Controllers', function () {
|
||||
it('should resize and upload an image to a post', function (done) {
|
||||
var oldValue = meta.config.maximumImageWidth;
|
||||
meta.config.maximumImageWidth = 10;
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: cid }, jar, csrf_token, function (err, res, body) {
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(Array.isArray(body));
|
||||
@@ -116,7 +107,7 @@ describe('Upload Controllers', function () {
|
||||
meta.config.allowFileUploads = 1;
|
||||
var oldValue = meta.config.allowedFileExtensions;
|
||||
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/503.html'), { cid: cid }, jar, csrf_token, function (err, res, body) {
|
||||
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, function (err, res, body) {
|
||||
meta.config.allowedFileExtensions = oldValue;
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
|
||||
Reference in New Issue
Block a user