diff --git a/install/data/defaults.json b/install/data/defaults.json index 3d45ff6565..547adb1334 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -38,5 +38,11 @@ "bookmarkThreshold": 5, "topicsPerList": 20, "autoDetectLang": 1, - "min:rep:flag": 0 + "min:rep:flag": 0, + "notificationType_upvote": "notification", + "notificationType_new-topic": "notification", + "notificationType_new-reply": "notification", + "notificationType_follow": "notification", + "notificationType_new-chat": "notification", + "notificationType_group-invite": "notification" } diff --git a/install/data/navigation.json b/install/data/navigation.json index 8c7965dc7e..3f47367ce8 100644 --- a/install/data/navigation.json +++ b/install/data/navigation.json @@ -70,16 +70,5 @@ "targetBlank": false, "adminOnly": true } - }, - { - "route": "/search", - "title": "[[global:header.search]]", - "enabled": true, - "iconClass": "fa-search", - "textClass": "visible-xs-inline", - "text": "[[global:header.search]]", - "properties": { - "searchInstalled": true - } } ] \ No newline at end of file diff --git a/install/package.json b/install/package.json index 2aa1c73db3..99411a5c3d 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.7.4", + "version": "1.7.5", "homepage": "http://www.nodebb.org", "repository": { "type": "git", @@ -25,6 +25,7 @@ "body-parser": "^1.18.2", "bootstrap": "^3.3.7", "chart.js": "^2.7.1", + "clipboard": "1.7.1", "colors": "^1.1.2", "compression": "^1.7.1", "commander": "^2.12.2", @@ -60,19 +61,19 @@ "mousetrap": "^1.6.1", "mubsub": "^1.4.0", "nconf": "^0.9.1", - "nodebb-plugin-composer-default": "6.0.12", + "nodebb-plugin-composer-default": "6.0.13", "nodebb-plugin-dbsearch": "2.0.9", "nodebb-plugin-emoji": "^2.1.0", "nodebb-plugin-emoji-android": "2.0.0", "nodebb-plugin-markdown": "8.3.0", "nodebb-plugin-mentions": "2.2.3", "nodebb-plugin-soundpack-default": "1.0.0", - "nodebb-plugin-spam-be-gone": "0.5.1", + "nodebb-plugin-spam-be-gone": "0.5.2", "nodebb-rewards-essentials": "0.0.11", - "nodebb-theme-lavender": "5.0.1", - "nodebb-theme-persona": "7.2.20", + "nodebb-theme-lavender": "5.0.3", + "nodebb-theme-persona": "7.2.23", "nodebb-theme-slick": "1.1.4", - "nodebb-theme-vanilla": "8.1.9", + "nodebb-theme-vanilla": "8.1.10", "nodebb-widget-essentials": "4.0.2", "nodemailer": "4.4.1", "passport": "^0.4.0", diff --git a/public/language/cs/admin/appearance/customise.json b/public/language/cs/admin/appearance/customise.json index 6fa4cbfbca..10876739b9 100644 --- a/public/language/cs/admin/appearance/customise.json +++ b/public/language/cs/admin/appearance/customise.json @@ -1,7 +1,7 @@ { - "custom-css": "Custom CSS/LESS", - "custom-css.description": "Enter your own CSS/LESS declarations here, which will be applied after all other styles.", - "custom-css.enable": "Enable Custom CSS/LESS", + "custom-css": "Uživatelský CSS/LESS", + "custom-css.description": "Zadejte vlastní definici CSS/LESS, která bude nadřazená ostatním stylům.", + "custom-css.enable": "Povolit uživatelský CSS/LESS", "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.", diff --git a/public/language/cs/admin/general/homepage.json b/public/language/cs/admin/general/homepage.json index 61d64ab3a7..3db45d23c3 100644 --- a/public/language/cs/admin/general/homepage.json +++ b/public/language/cs/admin/general/homepage.json @@ -4,5 +4,5 @@ "home-page-route": "Cesta k domovské stránce", "custom-route": "Upravit cestu", "allow-user-home-pages": "Povolit uživatelům domovské stránky", - "home-page-title": "Title of the home page (default \"Home\")" + "home-page-title": "Titulka domovské stránky (výchozí „Domů”)" } \ No newline at end of file diff --git a/public/language/cs/admin/manage/tags.json b/public/language/cs/admin/manage/tags.json index 8c4fb5a127..3a2e5682e2 100644 --- a/public/language/cs/admin/manage/tags.json +++ b/public/language/cs/admin/manage/tags.json @@ -6,7 +6,7 @@ "description": "Vyberte značky pomocí kliknutí a/nebo přetažením, pro vícenásobný výběr, použijte klávesu Shift.", "create": "Vytvořit značku", "modify": "Upravit značky", - "rename": "Rename Tags", + "rename": "Přejmenovat značky", "delete": "Odstranit vybrané značky", "search": "Hledat značky...", "settings": "Pro přejití na stránku s nastavením značek, klikněte zde.", diff --git a/public/language/cs/admin/manage/users.json b/public/language/cs/admin/manage/users.json index fe95fa070e..ea092e24da 100644 --- a/public/language/cs/admin/manage/users.json +++ b/public/language/cs/admin/manage/users.json @@ -5,14 +5,14 @@ "remove-admin": "Odebrat správce", "validate-email": "Ověřit e-mail", "send-validation-email": "Poslat ověřovací e-mail", - "password-reset-email": "Poslat e-mail pro resetování hesla", + "password-reset-email": "Poslat e-mail pro resetování hesla", "ban": "Zakázat uživatele", "temp-ban": "Dočasně zakázat uživatele", "unban": "Zrušit zákaz uživatele", "reset-lockout": "Obnovit uzamčení", "reset-flags": "Obnovit označení", "delete": "Odstranit uživatele", - "purge": "Odstranit uživatele a obsah", + "purge": "Odstranit uživatele a obsah", "download-csv": "Stáhnout jako CSV", "invite": "Pozvat", "new": "Nový uživatel", @@ -27,19 +27,19 @@ "pills.banned": "Zakázán", "pills.search": "Hledat uživatele", - "search.uid": "By User ID", - "search.uid-placeholder": "Enter a user ID to search", + "search.uid": "Dle ID uživatele", + "search.uid-placeholder": "Pro hledání, zadejte ID uživatele", "search.username": "Dle jména uživatele", "search.username-placeholder": "Zadejte hledané uživatelské jméno", "search.email": "Podle e-mailu", "search.email-placeholder": "Zadejte hledaný e-mail", - "search.ip": "Podle IP adresy", - "search.ip-placeholder": "Zadejte hledanou IP adresu", + "search.ip": "Podle IP adresy", + "search.ip-placeholder": "Zadejte hledanou IP adresu", "search.not-found": "Uživatel nebyl nalezen.", - "inactive.3-months": "3 měsíce", - "inactive.6-months": "6 měsíců", - "inactive.12-months": "12 měsíců", + "inactive.3-months": "3 měsíce", + "inactive.6-months": "6 měsíců", + "inactive.12-months": "12 měsíců", "users.uid": "uid", "users.username": "jméno", @@ -71,15 +71,15 @@ "alerts.lockout-reset-success": "Uzamčení bylo obnoveno.", "alerts.flag-reset-success": "Označení bylo obnoveno.", "alerts.no-remove-yourself-admin": "Sebe jako správce nemůžete vyjmout.", - "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 moderator.", - "alerts.make-moderator-success": "User is now moderator.", - "alerts.confirm-remove-moderator": "Do you really want to remove this moderator?", - "alerts.remove-moderator-success": "User is no longer moderator.", + "alerts.make-admin-success": "Uživatel je nyní správcem", + "alerts.confirm-remove-admin": "Opravdu chcete vyjmout tohoto správce?", + "alerts.remove-admin-success": "Uživatel již není správcem.", + "alerts.make-global-mod-success": "Uživatel je nyní globálním moderátorem.", + "alerts.confirm-remove-global-mod": "Opravdu chcete vyjmout tohoto globálního moderátora?", + "alerts.remove-global-mod-success": "Uživatel již není globálním moderátorem.", + "alerts.make-moderator-success": "Uživatel je nyní moderátorem.", + "alerts.confirm-remove-moderator": "Opravdu chcete vyjmout tohoto moderátora?", + "alerts.remove-moderator-success": "Uživatel není již moderátorem.", "alerts.confirm-validate-email": "Chcete schválit e-mailové adresy těchto uživatelů?", "alerts.validate-email-success": "E-maily byly ověřeny", "alerts.password-reset-confirm": "Chcete poslat těmto uživatelům e-mail pro resetování hesla?", @@ -95,5 +95,5 @@ "alerts.prompt-email": "E-mail:", "alerts.email-sent-to": "E-mail s pozvánkou byl odeslán na %1", - "alerts.x-users-found": "Počet nalezených uživatelů: %1 (hledání trvalo %2 ms)" + "alerts.x-users-found": "Počet nalezených uživatelů: %1 (hledání trvalo %2 ms)" } \ No newline at end of file diff --git a/public/language/cs/admin/menu.json b/public/language/cs/admin/menu.json index 739daa2b71..65b9ee394d 100644 --- a/public/language/cs/admin/menu.json +++ b/public/language/cs/admin/menu.json @@ -9,10 +9,10 @@ "section-manage": "Spravovat", "manage/categories": "Kategorie", - "manage/privileges": "Privileges", + "manage/privileges": "Oprávnění", "manage/tags": "Značky", "manage/users": "Uživatelé", - "manage/admins-mods": "Admins & Mods", + "manage/admins-mods": "Správci a moderátoři", "manage/registration": "Registrační fronta", "manage/post-queue": "Fronta příspěvků", "manage/groups": "Skupiny", @@ -70,8 +70,8 @@ "search.placeholder": "Hledat nastavení", "search.no-results": "Žádné výsledky…", "search.search-forum": "Prohledat fórum pro ", - "search.keep-typing": "Pište dále pro zobrazení výsledků…", - "search.start-typing": "Začněte psát pro zobrazení výsledků…", + "search.keep-typing": "Pište dále pro zobrazení výsledků…", + "search.start-typing": "Začněte psát pro zobrazení výsledků…", - "connection-lost": "Připojení k %1 bylo ztraceno, snaha o opětovné připojení…" + "connection-lost": "Připojení k %1 bylo ztraceno, snaha o opětovné připojení…" } \ No newline at end of file diff --git a/public/language/cs/admin/settings/chat.json b/public/language/cs/admin/settings/chat.json index da584e2fe4..57e5e4ad9c 100644 --- a/public/language/cs/admin/settings/chat.json +++ b/public/language/cs/admin/settings/chat.json @@ -6,6 +6,6 @@ "max-length": "Maximální délka konverzační zprávy", "max-room-size": "Maximální počet uživatelů v konverzační místnosti", "delay": "Čas mezi konverzačními zprávami v milisekundách", - "restrictions.seconds-edit-after": "Number of seconds before users are allowed to edit chat messages after posting. (0 disabled)", - "restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete chat messages after posting. (0 disabled)" + "restrictions.seconds-edit-after": "Počet sekund než je uživateli umožněno upravit zprávy konverzace po jejich odeslání. (0 zákaz)", + "restrictions.seconds-delete-after": "Počet sekund než je uživateli umožněno smazat zprávy konverzace po jejich odeslání. (0 zákaz)" } \ No newline at end of file diff --git a/public/language/cs/admin/settings/post.json b/public/language/cs/admin/settings/post.json index d55738c807..b8c9350e8e 100644 --- a/public/language/cs/admin/settings/post.json +++ b/public/language/cs/admin/settings/post.json @@ -6,25 +6,25 @@ "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", - "length": "Post Length", + "length": "Délka příspěvku", "restrictions": "Omezení příspěvků", - "restrictions-new": "New User Restrictions", + "restrictions-new": "Omezení nového uživatele", "restrictions.post-queue": "Povolit frontu pro příspěvky", - "restrictions-new.post-queue": "Enable new user restrictions", + "restrictions-new.post-queue": "Povolit omezení nových uživatelů", "restrictions.post-queue-help": "Povolení fronty příspěvků bude přidávat příspěvky nových uživatelů do fronty na schválení.", - "restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users.", - "restrictions.seconds-between": "Seconds between posts", - "restrictions.seconds-between-new": "Seconds between posts for new users", - "restrictions.rep-threshold": "Reputation threshold before these restrictions are lifted", + "restrictions-new.post-queue-help": "Povolení omezení nových uživatelů uvede v činnost omezení na vytvořené příspěvky nových uživatelů.", + "restrictions.seconds-between": "Sekund mezi příspěvky", + "restrictions.seconds-between-new": "Sekund mezi příspěvky pro nové uživatele", + "restrictions.rep-threshold": "Ohraničení reputace než začnou platit tato omezení", "restrictions.seconds-defore-new": "Sekundy předtím, než uživatel může přidat příspěvek", - "restrictions.seconds-edit-after": "Number of seconds before users are allowed to edit posts after posting. (0 disabled)", - "restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete posts after posting. (0 disabled)", + "restrictions.seconds-edit-after": "Počet sekund než bude moci uživatel upravit příspěvek před jeho odesláním. (0 zákaz)", + "restrictions.seconds-delete-after": "Počet sekund než bude moci uživatel smazat příspěvek před jeho odesláním. (0 zákaz)", "restrictions.replies-no-delete": "Počet odpovědí, kdy je uživatelům zakázáno odstranit jejich vlastní příspěvek. (0 zakázáno)", "restrictions.min-title-length": "Minimální délka názvu", "restrictions.max-title-length": "Maximální délka názvu", "restrictions.min-post-length": "Minimální délka příspěvku", "restrictions.max-post-length": "Maximální délka příspěvku", - "restrictions.days-until-stale": "Days until topic is considered stale", + "restrictions.days-until-stale": "Počet dnů, než je téma považováno za neaktuální", "restrictions.stale-help": "Je-li téma považováno za „staré”, uživateli se zobrazí oznámení při pokusu o přidání odpovědi.", "timestamp": "Časový otisk", "timestamp.cut-off": "Datum ukončení (ve dnech)", diff --git a/public/language/cs/admin/settings/reputation.json b/public/language/cs/admin/settings/reputation.json index 7339496268..a72f32ad00 100644 --- a/public/language/cs/admin/settings/reputation.json +++ b/public/language/cs/admin/settings/reputation.json @@ -6,7 +6,7 @@ "thresholds": "Omezení aktivity", "min-rep-downvote": "Minimální reputace pro vyjádření nesouhlasu s příspěvkem", "min-rep-flag": "Minimální reputace pro označení příspěvků", - "min-rep-website": "Minimum reputation to add \"Website\" to user profile", - "min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile", - "min-rep-signature": "Minimum reputation to add \"Signature\" to user profile" + "min-rep-website": "Minimální reputace pro přidání „Webové stránky” do uživatelského profilu", + "min-rep-aboutme": "Minimální reputace pro přidání „O mně” do uživatelského profilu", + "min-rep-signature": "Minimální reputace pro přidání „Podpisu” do uživatelského profilu" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index 6ad76f1ca6..01ff27c810 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -17,7 +17,7 @@ "invalid-login-credentials": "Neplatné přihlašovací údaje", "invalid-username-or-password": "Zadejte prosím uživatelské jméno a i heslo", "invalid-search-term": "Neplatný výraz pro vyhledávání", - "invalid-url": "Invalid URL", + "invalid-url": "Neplatné URL", "csrf-invalid": "Není možné vás přihlásit, díky vypršení relace. Zkuste to prosím znovu.", "invalid-pagination-value": "Neplatná hodnota stránkování, musí být alespoň %1 a nejvýše %2", "username-taken": "Uživatelské jméno je již použito", @@ -114,16 +114,16 @@ "cant-edit-chat-message": "Tuto zprávu nemůžete upravit", "cant-remove-last-user": "Posledního uživatele nemůžete vyjmout", "cant-delete-chat-message": "Tuto zprávu nemůžete odstranit", - "chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting", - "chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting", + "chat-edit-duration-expired": "Je vám umožněno upravit konverzační zprávy pod dobu %1 sekund/y po jejich odeslání", + "chat-delete-duration-expired": "Je vám umožněno odstranit konverzační zprávy pod dobu %1 sekund/y po jejich odeslání", "already-voting-for-this-post": "Již jste v tomto příspěvku hlasoval.", "reputation-system-disabled": "Systém reputací je zakázán.", "downvoting-disabled": "Systém nesouhlasu je zakázán", "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", - "not-enough-reputation-min-rep-website": "You do not have enough reputation to add a website", - "not-enough-reputation-min-rep-aboutme": "You do not have enough reputation to add an about me", - "not-enough-reputation-min-rep-signature": "You do not have enough reputation to add a signature", + "not-enough-reputation-min-rep-website": "Pro přidání webové stránky nemáte dostatek reputace", + "not-enough-reputation-min-rep-aboutme": "Pro přidání „O mně” nemáte dostatek reputace", + "not-enough-reputation-min-rep-signature": "Pro přidání podpisu nemáte dostatek reputace", "already-flagged": "Tento příspěvek jste již označil", "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.", diff --git a/public/language/cs/global.json b/public/language/cs/global.json index 90db3ca447..695a4f3946 100644 --- a/public/language/cs/global.json +++ b/public/language/cs/global.json @@ -53,7 +53,7 @@ "topics": "Témata", "posts": "Příspěvky", "best": "Nejlepší", - "votes": "Votes", + "votes": "Počet hlasů", "upvoters": "Souhlasník", "upvoted": "Souhlasů", "downvoters": "Nesouhlasník", @@ -85,7 +85,7 @@ "language": "Jazyk", "guest": "Host", "guests": "Hosté", - "updated.title": "Fórum zaktualizováno", + "updated.title": "Fórum bylo zaktualizováno", "updated.message": "Toto fórum bylo právě aktualizováno na poslední verzi. Klikněte zde a obnovte tuto stránku.", "privacy": "Soukromí", "follow": "Sledovat", @@ -101,7 +101,7 @@ "unsaved-changes": "Některé změny nebyly uloženy. Jste si jist, že chcete jít jinam?", "reconnecting-message": "Vypadá to, že vaše připojení k %1 bylo ukončeno. Vyčkejte prosím, než obnovíme připojení.", "play": "Přehrát", - "cookies.message": "Tato webová stránka používá \"cookies\", aby jste mohl plně zažít její funkčnost.", + "cookies.message": "Pro využití plné funkčnosti stránek, jsou použity „cookies”.", "cookies.accept": "Rozumím.", "cookies.learn_more": "Zjistit více", "edited": "Upraveno", diff --git a/public/language/cs/pages.json b/public/language/cs/pages.json index 151b0a0a4a..31267947ad 100644 --- a/public/language/cs/pages.json +++ b/public/language/cs/pages.json @@ -6,7 +6,7 @@ "popular-month": "Oblíbená témata pro tento měsíc", "popular-alltime": "Oblíbená témata za celou dobu", "recent": "Aktuální témata", - "top": "Top Voted Topics", + "top": "Témata s nejvíce hlasy", "moderator-tools": "Nástroje moderátora", "flagged-content": "Nahlášený obsah", "ip-blacklist": "Černá listina IP adres", @@ -20,7 +20,7 @@ "users/search": "Hledat uživatele", "notifications": "Upozornění", "tags": "Značky", - "tag": "Topics tagged under "%1"", + "tag": "Témata označená "%1"", "register": "Zaregistrovat účet", "registration-complete": "Registrace dokončena", "login": "Přihlásit se ke svému účtu", diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json index 2b55844f41..e9b469c64c 100644 --- a/public/language/cs/topic.json +++ b/public/language/cs/topic.json @@ -34,7 +34,7 @@ "flag_title": "Označit tento příspěvek k moderování", "deleted_message": "Toto téma bylo odstraněno. Jen uživatelé s oprávněním správy témat ho mohou vidět.", "following_topic.message": "Nyní budete dostávat upozornění, jakmile někdo přidá příspěvek do tohoto tématu.", - "not_following_topic.message": "Uvidíte toto téma v seznamu nepřečtených témat, ale neobdržíte upozornění. pokud sem někdo přidá příspěvek.", + "not_following_topic.message": " Toto téma uvidíte v seznamu nepřečtených témat, ale neobdržíte upozornění, přidá-li někdo nový příspěvek.", "ignoring_topic.message": "Již nadále neuvidíte toto téma v seznamu nepřečtených témat. Budete upozorněn, jakmile se někdo o vás zmíní nebo bude vyjádřen souhlas s příspěvkem.", "login_to_subscribe": "Pro sledování tohoto tématu se prosím přihlaste nebo zaregistrujte.", "markAsUnreadForAll.success": "Téma označeno jako nepřečtené pro všechny.", @@ -52,7 +52,7 @@ "not-watching.description": "Neupozorňovat na nové odpovědi.
Zobrazit téma v nepřečtených, není-li tato kategorie ignorována", "ignoring.description": "Neupozorňovat na nové odpovědi.
Nezobrazovat téma v nepřečtených.", "thread_tools.title": "Nástroje tématu", - "thread_tools.markAsUnreadForAll": "Mark Unread For All", + "thread_tools.markAsUnreadForAll": "Označit nepřečtené pro všechny", "thread_tools.pin": "Připnout téma", "thread_tools.unpin": "Odepnout téma", "thread_tools.lock": "Zamknout téma", diff --git a/public/language/en-GB/admin/general/navigation.json b/public/language/en-GB/admin/general/navigation.json index 9abf7f58cc..a199668ae2 100644 --- a/public/language/en-GB/admin/general/navigation.json +++ b/public/language/en-GB/admin/general/navigation.json @@ -14,9 +14,6 @@ "only-guest": "Only display to guests", "open-new-window": "Open in a new window", - "installed-plugins-required": "Installed Plugins Required:", - "search-plugin": "Search plugin", - "btn.delete": "Delete", "btn.disable": "Disable", "btn.enable": "Enable", diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index 871affe560..6aa607612d 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -65,5 +65,7 @@ "alert.find-user": "Find a User", "alert.user-search": "Search for a user here...", "alert.find-group": "Find a Group", - "alert.group-search": "Search for a group here..." + "alert.group-search": "Search for a group here...", + "collapse-all": "Collapse All", + "expand-all": "Expand All" } \ No newline at end of file diff --git a/public/language/en-GB/admin/manage/ip-blacklist.json b/public/language/en-GB/admin/manage/ip-blacklist.json index cd79294266..588fbd62b6 100644 --- a/public/language/en-GB/admin/manage/ip-blacklist.json +++ b/public/language/en-GB/admin/manage/ip-blacklist.json @@ -14,5 +14,6 @@ "alerts.applied-success": "Blacklist Applied", "analytics.blacklist-hourly": "Figure 1 – Blacklist hits per hour", - "analytics.blacklist-daily": "Figure 2 – Blacklist hits per day" + "analytics.blacklist-daily": "Figure 2 – Blacklist hits per day", + "ip-banned": "IP banned" } \ No newline at end of file diff --git a/public/language/en-GB/admin/settings/user.json b/public/language/en-GB/admin/settings/user.json index cbdd4ee91c..664bff67f7 100644 --- a/public/language/en-GB/admin/settings/user.json +++ b/public/language/en-GB/admin/settings/user.json @@ -62,5 +62,6 @@ "email-chat-notifs": "Send an email if a new chat message arrives and I am not online", "email-post-notif": "Send an email when replies are made to topics I am subscribed to", "follow-created-topics": "Follow topics you create", - "follow-replied-topics": "Follow topics that you reply to" + "follow-replied-topics": "Follow topics that you reply to", + "default-notification-settings": "Default notification settings" } \ No newline at end of file diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 8816e253be..14842a1507 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -161,6 +161,7 @@ "wrong-login-type-email": "Please use your email to login", "wrong-login-type-username": "Please use your username to login", "sso-registration-disabled": "Registration has been disabled for %1 accounts, please register with an email address first", + "sso-multiple-association": "You cannot associate multiple accounts from this service to your NodeBB account. Please dissociate your existing account and try again.", "invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2).", diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index a4892868b3..e084a9f24f 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -33,6 +33,8 @@ "locked": "Locked", "pinned": "Pinned", "moved": "Moved", + "copy-ip": "Copy IP", + "ban-ip": "Ban IP", "bookmark_instructions" : "Click here to return to the last read post in this thread.", diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index d1bf34e94a..093179d80f 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -104,7 +104,7 @@ "topics_per_page": "Topics per Page", "posts_per_page": "Posts per Page", "max_items_per_page": "Maximum %1", - + "acp_language": "Admin Page Language", "notification_sounds" : "Play a sound when you receive a notification", "notifications_and_sounds": "Notifications & Sounds", "incoming-message-sound": "Incoming message sound", diff --git a/public/language/fa-IR/admin/advanced/errors.json b/public/language/fa-IR/admin/advanced/errors.json index 546f0f1508..de75745bf6 100644 --- a/public/language/fa-IR/admin/advanced/errors.json +++ b/public/language/fa-IR/admin/advanced/errors.json @@ -1,14 +1,14 @@ { "figure-x": "Figure %1", "error-events-per-day": "%1 events per day", - "error.404": "404 Not Found", - "error.503": "503 Service Unavailable", - "manage-error-log": "Manage Error Log", + "error.404": "ارور 404 یافت نشد", + "error.503": "ارور 503 سرویس دردسترس نیست", + "manage-error-log": "مدیریت Error Log", "export-error-log": "Export Error Log (CSV)", - "clear-error-log": "Clear Error Log", - "route": "Route", - "count": "Count", - "no-routes-not-found": "Hooray! No 404 errors!", - "clear404-confirm": "Are you sure you wish to clear the 404 error logs?", - "clear404-success": "\"404 Not Found\" errors cleared" + "clear-error-log": "پاک کردن Error Log", + "route": "مسیر", + "count": "شمارش", + "no-routes-not-found": "ایول! بدون ارور 404 !", + "clear404-confirm": "آیا از پاک کردن ارور های 404 اطمینان دارید؟", + "clear404-success": "ارور های 404 پاک شدند" } \ No newline at end of file diff --git a/public/language/fa-IR/admin/advanced/events.json b/public/language/fa-IR/admin/advanced/events.json index 766eb5e951..37672285ce 100644 --- a/public/language/fa-IR/admin/advanced/events.json +++ b/public/language/fa-IR/admin/advanced/events.json @@ -1,6 +1,6 @@ { - "events": "Events", - "no-events": "There are no events", - "control-panel": "Events Control Panel", - "delete-events": "Delete Events" + "events": "رویداد ها", + "no-events": "رویدادی موجود نیست", + "control-panel": "کنترل پنل رویداد ها", + "delete-events": "پاک کردن رویداد ها" } \ No newline at end of file diff --git a/public/language/fa-IR/admin/advanced/logs.json b/public/language/fa-IR/admin/advanced/logs.json index b9de400e1c..37846be559 100644 --- a/public/language/fa-IR/admin/advanced/logs.json +++ b/public/language/fa-IR/admin/advanced/logs.json @@ -1,7 +1,7 @@ { - "logs": "Logs", - "control-panel": "Logs Control Panel", - "reload": "Reload Logs", - "clear": "Clear Logs", - "clear-success": "Logs Cleared!" + "logs": "گزارشات", + "control-panel": "کنترل پنل گزارشات", + "reload": "بارگزاری مجدد گزارش ها", + "clear": "حذف گزارشات", + "clear-success": "گزارش ها پاک شدند" } \ No newline at end of file diff --git a/public/language/fa-IR/admin/appearance/customise.json b/public/language/fa-IR/admin/appearance/customise.json index 56c11a2805..39aab64979 100644 --- a/public/language/fa-IR/admin/appearance/customise.json +++ b/public/language/fa-IR/admin/appearance/customise.json @@ -1,13 +1,13 @@ { - "custom-css": "Custom CSS/LESS", - "custom-css.description": "Enter your own CSS/LESS declarations here, which will be applied after all other styles.", - "custom-css.enable": "Enable Custom CSS/LESS", + "custom-css": "سفارشی کردن CSS/LESS", + "custom-css.description": "کد های CSS/LESS خود را در این قسمت وارد کنید . بعد از همه ی استایل های دیگر اعمال میشود", + "custom-css.enable": "به کار گرفتن CSS/LESS سفارشی", - "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": "جائا اسکریپت سفارشی", + "custom-js.description": "کد های جاوا اسکریپت خود را در این قسمت وارد کنید بعد از لود شدن تمام صفحه اجرا خواهند شد", + "custom-js.enable": "به کارگیری جاوا اسکریپت سفارشی ", - "custom-header": "Custom Header", + "custom-header": "هدر سفارشی", "custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <head> section of your forum's markup. Script tags are allowed, but are discouraged, as the Custom Javascript tab is available.", "custom-header.enable": "Enable Custom Header", diff --git a/public/language/fa-IR/notifications.json b/public/language/fa-IR/notifications.json index b80e40669c..c0b76b8c80 100644 --- a/public/language/fa-IR/notifications.json +++ b/public/language/fa-IR/notifications.json @@ -52,7 +52,7 @@ "email_only": "فقط ایمیل", "notification_and_email": "اعلان و ایمیل", "notificationType_upvote": "هنگامی که شخصی به پست شما رای مثبت می دهد", - "notificationType_new-topic": "هنگامی که شخصی که شما فالو می کنید موضوعی ایجاد نماید", + "notificationType_new-topic": "هنگامی که شخصی که شما دنبال می کنید موضوعی ایجاد نماید", "notificationType_new-reply": "هنگامی که پاسخ جدید در تاپیکی که شما پیگیری می کنید فرستاده می شود", "notificationType_follow": "هنگامی که کسی شما را دنبال می کند", "notificationType_new-chat": "هنگامی که شما پیام چتی دریافت می کنید", diff --git a/public/language/ru/global.json b/public/language/ru/global.json index 3bb8473b2c..777f9aa376 100644 --- a/public/language/ru/global.json +++ b/public/language/ru/global.json @@ -53,7 +53,7 @@ "topics": "Темы", "posts": "Записи", "best": "Лучшие", - "votes": "Votes", + "votes": "Голоса", "upvoters": "Кому понравилось", "upvoted": "Понравилось", "downvoters": "Кому не понравилось", @@ -105,6 +105,6 @@ "cookies.accept": "Понял", "cookies.learn_more": "Подробнее", "edited": "Отредактированный", - "disabled": "Disabled", - "select": "Select" + "disabled": "Отключено", + "select": "Выбрать" } \ No newline at end of file diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json index 442137ca92..f000b93b6b 100644 --- a/public/language/ru/modules.json +++ b/public/language/ru/modules.json @@ -20,7 +20,7 @@ "chat.three_months": "3 месяца", "chat.delete_message_confirm": "Вы уверены, что хотите удалить это сообщение?", "chat.add-users-to-room": "Добавить участников в комнату", - "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": "Этот пользователь установил статус \"Не беспокоить\". Вы все еще хотите написать ему?", "composer.compose": "Редактор сообщений", "composer.show_preview": "Показать предпросмотр сообщения", "composer.hide_preview": "Скрыть предпросмотр", diff --git a/public/language/ru/pages.json b/public/language/ru/pages.json index 7eabfad92f..ede173f40a 100644 --- a/public/language/ru/pages.json +++ b/public/language/ru/pages.json @@ -6,11 +6,11 @@ "popular-month": "Популярные темы этого месяца", "popular-alltime": "Популярные темы за всё время", "recent": "Последние темы", - "top": "Top Voted Topics", - "moderator-tools": "Moderator Tools", - "flagged-content": "Flagged Content", + "top": "Самые популярные темы", + "moderator-tools": "Инструменты модератора", + "flagged-content": "Выбранное содержимое", "ip-blacklist": "Чёрный список IP", - "post-queue": "Post Queue", + "post-queue": "Очередь публикации", "users/online": "В сети", "users/latest": "Новые участники", "users/sort-posts": "Участники по количеству сообщений", @@ -20,7 +20,7 @@ "users/search": "Поиск участников", "notifications": "Уведомления", "tags": "Метки", - "tag": "Topics tagged under "%1"", + "tag": "Темы помеченные как "%1"", "register": "Зарегистрироваться", "registration-complete": "Регистрация завершена", "login": "Войти", @@ -30,8 +30,8 @@ "group": "Группа %1", "chats": "Чаты", "chat": "Чат с участником %1", - "flags": "Flags", - "flag-details": "Flag %1 Details", + "flags": "Отметки", + "flag-details": "Отметка %1 детали", "account/edit": "Редактирование \"%1\"", "account/edit/password": "Сменить пароль \"%1\"", "account/edit/username": "Изменить имя пользователя \"%1\"", @@ -45,7 +45,7 @@ "account/bookmarks": "%1 сообщений в закладках", "account/settings": "Настройки учётной записи", "account/watched": "Тему просмотрели %1", - "account/ignored": "Topics ignored by %1", + "account/ignored": "Игнорируемые темы %1", "account/upvoted": "Рейтинг записей поднят %1", "account/downvoted": "Рейтинг записей снижен %1", "account/best": "Лучшие записи участника %1", diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json index 777e81e154..2563f68277 100644 --- a/public/language/ru/topic.json +++ b/public/language/ru/topic.json @@ -52,7 +52,7 @@ "not-watching.description": "Не уведомлять меня о новых ответах.
Показать тему в непрочитанных, если категория мной не игнорируется.", "ignoring.description": "Не уведомлять меня о новых ответах.
Не отображать тему в непрочитанных.", "thread_tools.title": "Настройки темы", - "thread_tools.markAsUnreadForAll": "Mark Unread For All", + "thread_tools.markAsUnreadForAll": "Выделить непрочитанные для всех", "thread_tools.pin": "Прикрепить тему", "thread_tools.unpin": "Открепить тему", "thread_tools.lock": "Закрыть тему", @@ -68,8 +68,8 @@ "thread_tools.restore_confirm": "Вы уверены, что хотите восстановить тему?", "thread_tools.purge": "Стереть тему", "thread_tools.purge_confirm": "Вы уверены, что хотите стереть эту тему?", - "thread_tools.merge_topics": "Merge Topics", - "thread_tools.merge": "Merge", + "thread_tools.merge_topics": "Объединить темы", + "thread_tools.merge": "Объединить", "topic_move_success": "Эта тема успешно перемещена в %1", "post_delete_confirm": "Вы уверены, что хотите удалить эту запись?", "post_restore_confirm": "Вы уверены, что хотите восстановить эту запись?", @@ -91,7 +91,7 @@ "fork_pid_count": "Отмечено %1 сообщений", "fork_success": "Готово! Просмотр отделённой темы.", "delete_posts_instruction": "Отметьте записи, которые вы хотите удалить", - "merge_topics_instruction": "Click the topics you want to merge", + "merge_topics_instruction": "Выберите темы которые вы хотите объединить", "composer.title_placeholder": "Введите название темы...", "composer.handle_placeholder": "Название", "composer.discard": "Отменить", diff --git a/public/language/ru/unread.json b/public/language/ru/unread.json index 1658c72740..bdead98f5d 100644 --- a/public/language/ru/unread.json +++ b/public/language/ru/unread.json @@ -10,6 +10,6 @@ "all-topics": "Все темы", "new-topics": "Новые темы", "watched-topics": "Подписанные темы", - "unreplied-topics": "Unreplied Topics", - "multiple-categories-selected": "Multiple Selected" + "unreplied-topics": "Неотвеченные темы", + "multiple-categories-selected": "Выбрано несколько" } \ No newline at end of file diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 242763bade..a30280a04e 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -25,7 +25,7 @@ "reputation": "Репутация", "bookmarks": "Закладки", "watched": "Подписка", - "ignored": "Ignored", + "ignored": "Игнорировать", "followers": "Подписчиков", "following": "Подписок", "aboutme": "Обо мне", @@ -34,7 +34,7 @@ "chat": "Чат", "chat_with": "Продолжить чат с %1", "new_chat_with": "Начать новый чат с %1", - "flag-profile": "Flag Profile", + "flag-profile": "Идентификатор профиля", "follow": "Подписаться", "unfollow": "Отписаться", "more": "Больше", @@ -85,7 +85,7 @@ "has_no_posts": "Участник пока не создал ни одной записи", "has_no_topics": "Участник пока не создал ни одной темы", "has_no_watched_topics": "Участник пока не посмотрел ни одной темы", - "has_no_ignored_topics": "This user hasn't ignored any topics yet.", + "has_no_ignored_topics": "Этот пользователь еще не игнорировал ни одной темы.", "has_no_upvoted_posts": "Участник пока не голосовал положительно ни за одну запись", "has_no_downvoted_posts": "Участник пока не голосовал против ни одной записи", "has_no_voted_posts": "Участник пока не голосовал ни за одну запись", @@ -94,18 +94,18 @@ "paginate_description": "Разбить на страницы, а не выводить бесконечным списком", "topics_per_page": "Тем на странице", "posts_per_page": "Записей на странице", - "max_items_per_page": "Maximum %1", + "max_items_per_page": "Максимум %1", "notification_sounds": "Воспроизводить звук во время получения уведомления", "notifications_and_sounds": "Уведомления и звуки", "incoming-message-sound": "Звук входящего сообщения", "outgoing-message-sound": "Звук исходящего сообщения", "notification-sound": "Звук уведомления", "no-sound": "Без звука", - "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": "Частота уведомлений о понравившемся отзыве", + "upvote-notif-freq.all": "Все положительные отзывы", + "upvote-notif-freq.everyTen": "Каждые десять понравившихся отзывов", + "upvote-notif-freq.logarithmic": "На 10, 100, 1000...", + "upvote-notif-freq.disabled": "Выключено", "browsing": "Настройки просмотра", "open_links_in_new_tab": "Открывать внешние ссылки в новом окне", "enable_topic_searching": "Поиск во всех записях темы", @@ -126,9 +126,9 @@ "sso.title": "Для вашего удобства вы можете связать ваши учётные записи на других социальных сервисах с учётной записью на нашем сайте", "sso.associated": "Связан с", "sso.not-associated": "Нажмите здесь, что бы связать учётную запись с", - "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": "Открепить", + "sso.dissociate-confirm-title": "Подтверждение открепления", + "sso.dissociate-confirm": "Вы уверены, что хотите открепить свой аккаунт от %1?", "info.latest-flags": "Новые отмеченные сообщения", "info.no-flags": "Отмеченных сообщений не найдено", "info.ban-history": "Недавно заблокированы", diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 5d211d00ca..2cd4914ce0 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -37,6 +37,20 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri el.find('i').toggleClass('fa-minus').toggleClass('fa-plus'); el.closest('[data-cid]').find('> ul[data-cid]').toggleClass('hidden'); }); + + $('#collapse-all').on('click', function () { + toggleAll(false); + }); + + $('#expand-all').on('click', function () { + toggleAll(true); + }); + + function toggleAll(expand) { + var el = $('.categories .toggle'); + el.find('i').toggleClass('fa-minus', expand).toggleClass('fa-plus', !expand); + el.closest('[data-cid]').find('> ul[data-cid]').toggleClass('hidden', !expand); + } }; Categories.throwCreateModal = function () { diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index 493afc91f0..803a3f923c 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -89,7 +89,7 @@ define('admin/settings', ['uploader'], function (uploader) { alert_id: 'config_status', timeout: 2500, title: 'Changes Not Saved', - message: 'NodeBB encountered a problem saving your changes', + message: 'NodeBB encountered a problem saving your changes. (' + err.message + ')', type: 'danger', }); } diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index bffefc725f..2acd578dd9 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -55,7 +55,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' if (skinName === 'default') { skinName = config.defaultBootswatchSkin; } - var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css'; + var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/3.3.7/' + skinName + '/bootstrap.min.css'; if (css.length) { css.attr('href', cssSource); } else { diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 54052d47c1..63c63e3051 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -231,7 +231,7 @@ define('forum/topic', [ var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; var currentBookmark = ajaxify.data.bookmark || storage.getItem(bookmarkKey); - if (ajaxify.data.postcount > ajaxify.data.bookmarkThreshold && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) { + if (ajaxify.data.postcount > ajaxify.data.bookmarkThreshold && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10) || ajaxify.data.postcount < parseInt(currentBookmark, 10))) { if (app.user.uid) { socket.emit('topics.bookmark', { tid: ajaxify.data.tid, diff --git a/public/src/client/topic/merge.js b/public/src/client/topic/merge.js index 473f76583c..510acf6765 100644 --- a/public/src/client/topic/merge.js +++ b/public/src/client/topic/merge.js @@ -10,9 +10,6 @@ define('forum/topic/merge', function () { Merge.init = function () { $('.category').on('click', '[component="topic/merge"]', onMergeTopicsClicked); - if (modal) { - $('[component="category/topic"]').on('click', 'a', onTopicClicked); - } }; function onMergeTopicsClicked() { @@ -28,7 +25,7 @@ define('forum/topic/merge', function () { modal.find('.close,#merge_topics_cancel').on('click', closeModal); - $('[component="category/topic"]').on('click', 'a', onTopicClicked); + $('[component="category"]').on('click', '[component="category/topic"] a', onTopicClicked); showTopicsSelected(); @@ -40,15 +37,19 @@ define('forum/topic/merge', function () { function onTopicClicked(ev) { var tid = $(this).parents('[component="category/topic"]').attr('data-tid'); - var index = $(this).parents('[component="category/topic"]').attr('data-index'); - var title = ajaxify.data.topics[index] ? ajaxify.data.topics[index].title : 'No title'; - if (selectedTids[tid]) { - delete selectedTids[tid]; - } else { - selectedTids[tid] = title; - } - checkButtonEnable(); - showTopicsSelected(); + socket.emit('topics.getTopic', tid, function (err, topicData) { + if (err) { + return app.alertError(err); + } + var title = topicData ? topicData.title : 'No title'; + if (selectedTids[tid]) { + delete selectedTids[tid]; + } else { + selectedTids[tid] = title; + } + checkButtonEnable(); + showTopicsSelected(); + }); ev.preventDefault(); ev.stopPropagation(); return false; diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 934a3942b7..9cbfdbb366 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -8,8 +8,7 @@ define('forum/topic/postTools', [ 'translator', 'forum/topic/votes', 'forum/topic/move-post', - 'benchpress', -], function (share, navigator, components, translator, votes, movePost, Benchpress) { +], function (share, navigator, components, translator, votes, movePost) { var PostTools = {}; var staleReplyAnyway = false; @@ -45,11 +44,12 @@ define('forum/topic/postTools', [ } data.posts.display_move_tools = data.posts.display_move_tools && index !== 0; - Benchpress.parse('partials/topic/post-menu-list', data, function (html) { - translator.translate(html, function (html) { - dropdownMenu.html(html); - $(window).trigger('action:post.tools.load'); + app.parseAndTranslate('partials/topic/post-menu-list', data, function (html) { + dropdownMenu.html(html); + require(['clipboard'], function (clipboard) { + new clipboard('[data-clipboard-text]'); }); + $(window).trigger('action:post.tools.load'); }); }); }); @@ -192,6 +192,16 @@ define('forum/topic/postTools', [ movePost.openMovePostModal($(this)); }); + postContainer.on('click', '[component="post/ban-ip"]', function () { + var ip = $(this).attr('data-ip'); + socket.emit('blacklist.addRule', ip, function (err) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('[[admin/manage/blacklist:ban-ip]]'); + }); + }); + postContainer.on('click', '[component="post/chat"]', function () { openChat($(this)); }); diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index d682faf947..0a3eea6b26 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -46,8 +46,7 @@ if ((properties.loggedIn && !loggedIn) || (properties.guestOnly && loggedIn) || (properties.globalMod && !data.isGlobalMod && !data.isAdmin) || - (properties.adminOnly && !data.isAdmin) || - (properties.searchInstalled && !data.searchEnabled)) { + (properties.adminOnly && !data.isAdmin)) { return false; } } diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index d9f29cebcd..92574f3eb1 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -77,7 +77,7 @@ define('notifications', ['sounds', 'translator', 'components', 'navigator', 'ben payload.message = notifData.bodyShort; payload.type = 'info'; payload.clickfn = function () { - if (notifData.path.startsWith('http') && notifData.path.startsWith('https')) { + if (notifData.path.startsWith('http') || notifData.path.startsWith('https')) { window.location.href = notifData.path; } else { window.location.href = window.location.protocol + '//' + window.location.host + config.relative_path + notifData.path; diff --git a/public/src/utils.js b/public/src/utils.js index d921573f7a..77c90c4fd9 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -518,7 +518,15 @@ } }, - tags: ['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr'], + tags: ['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', + 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', + 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', + 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', + 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', + 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', + 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', + 'small', 'source', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', + 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr'], stripTags: ['abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blink', 'body', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', diff --git a/src/categories/update.js b/src/categories/update.js index 866e6cd748..c0572f1ffb 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -41,7 +41,7 @@ module.exports = function (Categories) { } }, function (next) { - plugins.fireHook('filter:category.update', { category: modifiedFields }, next); + plugins.fireHook('filter:category.update', { cid: cid, category: modifiedFields }, next); }, function (categoryData, next) { category = categoryData.category; diff --git a/src/cli/package-install.js b/src/cli/package-install.js index af245b08fb..7baaee2a2b 100644 --- a/src/cli/package-install.js +++ b/src/cli/package-install.js @@ -41,11 +41,18 @@ function installAll() { } catch (e) { // ignore } - - cproc.execSync(command + (prod ? ' --production' : ''), { - cwd: path.join(__dirname, '../../'), - stdio: [0, 1, 2], - }); + try { + cproc.execSync(command + (prod ? ' --production' : ''), { + cwd: path.join(__dirname, '../../'), + stdio: [0, 1, 2], + }); + } catch (e) { + console.log('Error installing dependencies!'); + console.log('message: ' + e.message); + console.log('stdout: ' + e.stdout); + console.log('stderr: ' + e.stderr); + throw e; + } } exports.installAll = installAll; diff --git a/src/cli/upgrade.js b/src/cli/upgrade.js index b9cd46e4a2..befd627daf 100644 --- a/src/cli/upgrade.js +++ b/src/cli/upgrade.js @@ -65,7 +65,7 @@ function runSteps(tasks) { async.series(tasks, function (err) { if (err) { - console.error('Error occurred during upgrade'); + console.error('Error occurred during upgrade: ' + err.stack); throw err; } diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index cadb7c12f8..5166222706 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -1,6 +1,7 @@ 'use strict'; var async = require('async'); +var _ = require('lodash'); var user = require('../../user'); var languages = require('../../languages'); @@ -40,6 +41,9 @@ settingsController.get = function (req, res, callback) { function (results, next) { userData.settings = results.settings; userData.languages = results.languages; + if (userData.isAdmin && userData.isSelf) { + userData.acpLanguages = _.cloneDeep(results.languages); + } var types = [ 'notification', @@ -135,6 +139,12 @@ settingsController.get = function (req, res, callback) { language.selected = language.code === userData.settings.userLang; }); + if (userData.isAdmin && userData.isSelf) { + userData.acpLanguages.forEach(function (language) { + language.selected = language.code === userData.settings.acpLang; + }); + } + var notifFreqOptions = [ 'all', 'everyTen', @@ -203,7 +213,7 @@ function getNotificationSettings(userData, callback) { }, function (results, next) { function modifyType(type) { - var setting = userData.settings[type] || 'notification'; + var setting = userData.settings[type]; return { name: type, diff --git a/src/controllers/admin/blacklist.js b/src/controllers/admin/blacklist.js index a5f8136463..23d66d7c8a 100644 --- a/src/controllers/admin/blacklist.js +++ b/src/controllers/admin/blacklist.js @@ -7,7 +7,6 @@ var analytics = require('../../analytics'); var blacklistController = module.exports; blacklistController.get = function (req, res, next) { - // Analytics.getBlacklistAnalytics async.parallel({ rules: async.apply(meta.blacklist.get), analytics: async.apply(analytics.getBlacklistAnalytics), diff --git a/src/controllers/api.js b/src/controllers/api.js index 44ce2a582b..c64d054595 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -88,6 +88,7 @@ apiController.loadConfig = function (req, callback) { config.topicsPerPage = settings.topicsPerPage; config.postsPerPage = settings.postsPerPage; config.userLang = (req.query.lang ? validator.escape(String(req.query.lang)) : null) || settings.userLang || config.defaultLang; + config.acpLang = (req.query.lang ? validator.escape(String(req.query.lang)) : null) || settings.acpLang; config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab; config.topicPostSort = settings.topicPostSort || config.topicPostSort; config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort; diff --git a/src/controllers/index.js b/src/controllers/index.js index 4c4fa0ade5..ebb334d61c 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -233,6 +233,7 @@ Controllers.robots = function (req, res) { } else { res.send('User-agent: *\n' + 'Disallow: ' + nconf.get('relative_path') + '/admin/\n' + + 'Disallow: ' + nconf.get('relative_path') + '/reset/\n' + 'Sitemap: ' + nconf.get('url') + '/sitemap.xml'); } }; diff --git a/src/flags.js b/src/flags.js index 7bbb5168f8..8ea298b8e8 100644 --- a/src/flags.js +++ b/src/flags.js @@ -645,10 +645,18 @@ Flags.notify = function (flagObj, uid, callback) { admins: async.apply(groups.getMembers, 'administrators', 0, -1), globalMods: async.apply(groups.getMembers, 'Global Moderators', 0, -1), moderators: function (next) { + var cid; async.waterfall([ async.apply(posts.getCidByPid, flagObj.targetId), - function (cid, next) { - groups.getMembers('cid:' + cid + ':privileges:moderate', 0, -1, next); + function (_cid, next) { + cid = _cid; + groups.getMembers('cid:' + cid + ':privileges:groups:moderate', 0, -1, next); + }, + function (moderatorGroups, next) { + groups.getMembersOfGroups(moderatorGroups.concat(['cid:' + cid + ':privileges:moderate']), next); + }, + function (members, next) { + next(null, _.flatten(members)); }, ], next); }, diff --git a/src/groups/create.js b/src/groups/create.js index a8297b946b..9e0678a36f 100644 --- a/src/groups/create.js +++ b/src/groups/create.js @@ -8,13 +8,14 @@ var db = require('../database'); module.exports = function (Groups) { Groups.create = function (data, callback) { - var system = isSystemGroup(data); + var isSystem = isSystemGroup(data); var groupData; var timestamp = data.timestamp || Date.now(); var disableJoinRequests = parseInt(data.disableJoinRequests, 10) === 1 ? 1 : 0; if (data.name === 'administrators') { disableJoinRequests = 1; } + var isHidden = parseInt(data.hidden, 10) === 1; async.waterfall([ function (next) { validateGroupName(data.name, next); @@ -38,8 +39,8 @@ module.exports = function (Groups) { description: data.description || '', memberCount: memberCount, deleted: 0, - hidden: parseInt(data.hidden, 10) === 1 ? 1 : 0, - system: system ? 1 : 0, + hidden: isHidden ? 1 : 0, + system: isSystem ? 1 : 0, private: isPrivate, disableJoinRequests: disableJoinRequests, }; @@ -58,7 +59,7 @@ module.exports = function (Groups) { groupData.ownerUid = data.ownerUid; } - if (!data.hidden && !system) { + if (!isHidden && !isSystem) { tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:createtime', timestamp, groupData.name)); tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:memberCount', groupData.memberCount, groupData.name)); tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:name', 0, groupData.name.toLowerCase() + ':' + groupData.name)); diff --git a/src/languages.js b/src/languages.js index cdf56bf81d..65d5c2113d 100644 --- a/src/languages.js +++ b/src/languages.js @@ -71,12 +71,13 @@ Languages.list = function (callback) { if (err) { return next(err); } + var lang; try { - var lang = JSON.parse(file); - next(null, lang); + lang = JSON.parse(file); } catch (e) { - next(e); + return next(e); } + next(null, lang); }); }, function (err, languages) { if (err) { diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js index 41f09c5125..1bdbd3cef4 100644 --- a/src/messaging/rooms.js +++ b/src/messaging/rooms.js @@ -177,9 +177,44 @@ module.exports = function (Messaging) { })); db.sortedSetsRemove(keys, roomId, next); }, + function (next) { + updateOwner(roomId, next); + }, ], callback); }; + Messaging.leaveRooms = function (uid, roomIds, callback) { + async.waterfall([ + function (next) { + var roomKeys = roomIds.map(function (roomId) { + return 'chat:room:' + roomId + ':uids'; + }); + db.sortedSetsRemove(roomKeys, uid, next); + }, + function (next) { + db.sortedSetRemove('uid:' + uid + ':chat:rooms', roomIds, next); + }, + function (next) { + db.sortedSetRemove('uid:' + uid + ':chat:rooms:unread', roomIds, next); + }, + function (next) { + async.eachSeries(roomIds, updateOwner, next); + }, + ], callback); + }; + + function updateOwner(roomId, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRange('chat:room:' + roomId + ':uids', 0, 0, next); + }, + function (uids, next) { + var newOwner = uids[0] || 0; + db.setObjectField('chat:room:' + roomId, 'owner', newOwner, next); + }, + ], callback); + } + Messaging.getUidsInRoom = function (roomId, start, stop, callback) { db.getSortedSetRevRange('chat:room:' + roomId + ':uids', start, stop, callback); }; diff --git a/src/meta/blacklist.js b/src/meta/blacklist.js index 2b2f897284..c441c71b95 100644 --- a/src/meta/blacklist.js +++ b/src/meta/blacklist.js @@ -9,9 +9,8 @@ var pubsub = require('../pubsub'); var plugins = require('../plugins'); var analytics = require('../analytics'); -var Blacklist = { - _rules: [], -}; +var Blacklist = module.exports; +Blacklist._rules = []; Blacklist.load = function (callback) { callback = callback || function () {}; @@ -182,4 +181,22 @@ Blacklist.validate = function (rules, callback) { }); }; -module.exports = Blacklist; +Blacklist.addRule = function (rule, callback) { + var valid; + async.waterfall([ + function (next) { + Blacklist.validate(rule, next); + }, + function (result, next) { + valid = result.valid; + if (!valid.length) { + return next(new Error('[[error:invalid-rule]]')); + } + Blacklist.get(next); + }, + function (rules, next) { + rules = rules + '\n' + valid[0]; + Blacklist.save(rules, next); + }, + ], callback); +}; diff --git a/src/meta/configs.js b/src/meta/configs.js index 16445a2c32..bf4bfbb907 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -4,6 +4,7 @@ var async = require('async'); var nconf = require('nconf'); var path = require('path'); +var winston = require('winston'); var db = require('../database'); var pubsub = require('../pubsub'); @@ -83,9 +84,17 @@ function processConfig(data, callback) { var image = require('../image'); if (data['brand:logo']) { image.size(path.join(nconf.get('upload_path'), 'system', 'site-logo-x50.png'), function (err, size) { - if (err) { + if (err && err.code === 'ENOENT') { + // For whatever reason the x50 logo wasn't generated, gracefully error out + winston.warn('[logo] The email-safe logo doesn\'t seem to have been created, please re-upload your site logo.'); + size = { + height: 0, + width: 0, + }; + } else if (err) { return next(err); } + data['brand:emailLogo:height'] = size.height; data['brand:emailLogo:width'] = size.width; next(); diff --git a/src/meta/js.js b/src/meta/js.js index fb3d12f683..a6dc73331e 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -98,6 +98,7 @@ JS.scripts = { 'jqueryui.js': 'public/vendor/jquery/js/jquery-ui.js', 'zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js', ace: 'node_modules/ace-builds/src-min', + 'clipboard.js': 'node_modules/clipboard/dist/clipboard.min.js', }, }; diff --git a/src/middleware/admin.js b/src/middleware/admin.js index 2d0968d50f..3086f045cc 100644 --- a/src/middleware/admin.js +++ b/src/middleware/admin.js @@ -38,7 +38,7 @@ module.exports = function (middleware) { plugins: [], authentication: [], }; - + res.locals.config = res.locals.config || {}; async.waterfall([ function (next) { async.parallel({ @@ -51,9 +51,6 @@ module.exports = function (middleware) { custom_header: function (next) { plugins.fireHook('filter:admin.header.build', custom_header, next); }, - config: function (next) { - controllers.api.getConfig(req, res, next); - }, configs: function (next) { meta.configs.list(next); }, @@ -64,8 +61,6 @@ module.exports = function (middleware) { userData.uid = req.uid; userData['email:confirmed'] = parseInt(userData['email:confirmed'], 10) === 1; - res.locals.config = results.config; - var acpPath = req.path.slice(1).split('/'); acpPath.forEach(function (path, i) { acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); @@ -73,9 +68,9 @@ module.exports = function (middleware) { acpPath = acpPath.join(' > '); var templateValues = { - config: results.config, - configJSON: jsesc(JSON.stringify(results.config), { isScriptContext: true }), - relative_path: results.config.relative_path, + config: res.locals.config, + configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }), + relative_path: res.locals.config.relative_path, adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), user: userData, userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }), diff --git a/src/middleware/header.js b/src/middleware/header.js index 386304806e..7e192e449b 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -283,7 +283,7 @@ module.exports = function (middleware) { } if (skinToUse) { - obj.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinToUse + '/bootstrap.min.css'; + obj.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/3.3.7/' + skinToUse + '/bootstrap.min.css'; } } } diff --git a/src/middleware/index.js b/src/middleware/index.js index da4f4198a7..a28fb538e2 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -237,12 +237,18 @@ middleware.templatesOnDemand = function (req, res, next) { fs.readFile(filePath.replace(/\.js$/, '.tpl'), 'utf8', cb); }, function (source, cb) { + if (!source) { + return cb(new Error('[[error:templatesOnDemand.source-template-empty]]')); + } Benchpress.precompile({ source: source, minify: global.env !== 'development', }, cb); }, function (compiled, cb) { + if (!compiled) { + return cb(new Error('[[error:templatesOnDemand.compiled-template-empty]]')); + } fs.writeFile(filePath, compiled, cb); }, ], function (err) { diff --git a/src/middleware/render.js b/src/middleware/render.js index a4571b6879..1ab21ea351 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -41,8 +41,7 @@ module.exports = function (middleware) { options.template = { name: template }; options.template[template] = true; options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); - options.bodyClass = buildBodyClass(req, options); - + options.bodyClass = buildBodyClass(req, res, options); plugins.fireHook('filter:' + template + '.build', { req: req, res: res, templateData: options }, next); }, function (data, next) { @@ -120,13 +119,16 @@ module.exports = function (middleware) { function translate(str, req, res, next) { var language = (res.locals.config && res.locals.config.userLang) || 'en-GB'; + if (res.locals.renderAdminHeader) { + language = (res.locals.config && res.locals.config.acpLang) || 'en-GB'; + } language = req.query.lang ? validator.escape(String(req.query.lang)) : language; translator.translate(str, language, function (translated) { next(null, translator.unescape(translated)); }); } - function buildBodyClass(req, templateData) { + function buildBodyClass(req, res, templateData) { var clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); var parts = clean.split('/').slice(0, 3); parts.forEach(function (p, index) { @@ -145,6 +147,7 @@ module.exports = function (middleware) { parts.push('page-topic-category-' + utils.slugify(templateData.category.name)); } + parts.push('page-status-' + res.statusCode); return parts.join(' '); } }; diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 020ea4e024..c555424377 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -82,23 +82,20 @@ module.exports = function (Plugins) { var hookList = Plugins.loadedHooks[hook]; var hookType = hook.split(':')[0]; - try { - switch (hookType) { - case 'filter': - fireFilterHook(hook, hookList, params, callback); - break; - case 'action': - fireActionHook(hook, hookList, params, callback); - break; - case 'static': - fireStaticHook(hook, hookList, params, callback); - break; - default: - winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook); - break; - } - } catch (err) { - callback(err); + switch (hookType) { + case 'filter': + fireFilterHook(hook, hookList, params, callback); + break; + case 'action': + fireActionHook(hook, hookList, params, callback); + break; + case 'static': + fireStaticHook(hook, hookList, params, callback); + break; + default: + winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook); + callback(); + break; } }; diff --git a/src/routes/feeds.js b/src/routes/feeds.js index ccb5af1d76..a60b3b4f3d 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -106,7 +106,7 @@ function generateForTopic(req, res, callback) { var author = topicData.posts.length ? topicData.posts[0].username : ''; var feed = new rss({ - title: utils.stripHTMLTags(topicData.title, utils.stripTags), + title: utils.stripHTMLTags(topicData.title, utils.tags), description: description, feed_url: nconf.get('url') + '/topic/' + tid + '.rss', site_url: nconf.get('url') + '/topic/' + topicData.slug, @@ -125,7 +125,7 @@ function generateForTopic(req, res, callback) { dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString(); feed.item({ - title: 'Reply to ' + utils.stripHTMLTags(topicData.title, utils.stripTags) + ' on ' + dateStamp, + title: 'Reply to ' + utils.stripHTMLTags(topicData.title, utils.tags) + ' on ' + dateStamp, description: postData.content, url: nconf.get('url') + '/post/' + postData.pid, author: postData.user ? postData.user.username : '', @@ -301,7 +301,7 @@ function generateTopicsFeed(feedOptions, feedTopics, callback) { async.each(feedTopics, function (topicData, next) { var feedItem = { - title: utils.stripHTMLTags(topicData.title, utils.stripTags), + title: utils.stripHTMLTags(topicData.title, utils.tags), url: nconf.get('url') + '/topic/' + topicData.slug, date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString(), }; diff --git a/src/socket.io/blacklist.js b/src/socket.io/blacklist.js index 1435d20737..d4f1481508 100644 --- a/src/socket.io/blacklist.js +++ b/src/socket.io/blacklist.js @@ -26,3 +26,18 @@ SocketBlacklist.save = function (socket, rules, callback) { }, ], callback); }; + +SocketBlacklist.addRule = function (socket, rule, callback) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(socket.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return callback(new Error('[[error:no-privileges]]')); + } + + meta.blacklist.addRule(rule, next); + }, + ], callback); +}; diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index ae4c2b0b5a..4679b73598 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -113,6 +113,7 @@ SocketHelpers.sendNotificationToPostOwner = function (pid, fromuid, command, not bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]', bodyLong: results.postObj.content, pid: pid, + tid: postData.tid, path: '/post/' + pid, nid: command + ':post:' + pid + ':uid:' + fromuid, from: fromuid, diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index a9bbc6137b..a61e50ec6c 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -10,6 +10,8 @@ var socketTopics = require('../topics'); var privileges = require('../../privileges'); var plugins = require('../../plugins'); var social = require('../../social'); +var user = require('../../user'); + module.exports = function (SocketPosts) { SocketPosts.loadPostTools = function (socket, data, callback) { @@ -20,10 +22,16 @@ module.exports = function (SocketPosts) { function (next) { async.parallel({ posts: function (next) { - posts.getPostFields(data.pid, ['deleted', 'bookmarks', 'uid'], next); + posts.getPostFields(data.pid, ['deleted', 'bookmarks', 'uid', 'ip'], next); + }, + isAdmin: function (next) { + user.isAdministrator(socket.uid, next); + }, + isGlobalMod: function (next) { + user.isGlobalModerator(socket.uid, next); }, - isAdminOrMod: function (next) { - privileges.categories.isAdminOrMod(data.cid, socket.uid, next); + isModerator: function (next) { + user.isModerator(socket.uid, data.cid, next); }, canEdit: function (next) { privileges.posts.canEdit(data.pid, socket.uid, next); @@ -54,7 +62,12 @@ module.exports = function (SocketPosts) { results.posts.display_delete_tools = results.canDelete.flag; results.posts.display_flag_tools = socket.uid && !results.posts.selfPost && results.canFlag.flag; results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools; - results.posts.display_move_tools = results.isAdminOrMod; + results.posts.display_move_tools = results.isAdmin || results.isModerator; + results.posts.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !results.posts.selfPost; + + if (!results.isAdmin && !results.isGlobalMod && !results.isModerator) { + results.posts.ip = undefined; + } next(null, results); }, ], callback); diff --git a/src/socket.io/user/status.js b/src/socket.io/user/status.js index 8849f0210e..ccae98ff4c 100644 --- a/src/socket.io/user/status.js +++ b/src/socket.io/user/status.js @@ -39,6 +39,13 @@ module.exports = function (SocketUser) { function (next) { user.setUserFields(socket.uid, data, next); }, + function (next) { + if (status !== 'offline') { + user.updateOnlineUsers(socket.uid, next); + } else { + next(); + } + }, function (next) { var data = { uid: socket.uid, diff --git a/src/topics/bookmarks.js b/src/topics/bookmarks.js index 975a0d54f8..d1782b6713 100644 --- a/src/topics/bookmarks.js +++ b/src/topics/bookmarks.js @@ -4,7 +4,7 @@ var async = require('async'); var db = require('../database'); -var posts = require('../posts'); +var user = require('../user'); module.exports = function (Topics) { Topics.getUserBookmark = function (tid, uid, callback) { @@ -34,7 +34,9 @@ module.exports = function (Topics) { }; Topics.updateTopicBookmarks = function (tid, pids, callback) { + var minIndex; var maxIndex; + var postIndices; async.waterfall([ function (next) { @@ -42,38 +44,54 @@ module.exports = function (Topics) { }, function (postcount, next) { maxIndex = postcount; - Topics.getTopicBookmarks(tid, next); + + db.sortedSetRanks('tid:' + tid + ':posts', pids, next); }, - function (bookmarks, next) { - var forkedPosts = pids.map(function (pid) { - return { pid: pid, tid: tid }; + function (indices, next) { + postIndices = indices.map(function (i) { + return i === null ? 0 : i + 1; }); + minIndex = Math.min.apply(Math, postIndices); + Topics.getTopicBookmarks(tid, next); + }, + function (bookmarks, next) { var uidData = bookmarks.map(function (bookmark) { return { uid: bookmark.value, - bookmark: bookmark.score, + bookmark: parseInt(bookmark.score, 10), }; + }).filter(function (data) { + return data.bookmark >= minIndex; }); async.eachLimit(uidData, 50, function (data, next) { - posts.getPostIndices(forkedPosts, data.uid, function (err, postIndices) { - if (err) { - return next(err); + var bookmark = data.bookmark; + bookmark = Math.min(bookmark, maxIndex); + + postIndices.forEach(function (i) { + if (i < data.bookmark) { + bookmark -= 1; } + }); - var bookmark = data.bookmark; - bookmark = bookmark < maxIndex ? bookmark : maxIndex; + // make sure the bookmark is valid if we removed the last post + bookmark = Math.min(bookmark, maxIndex - pids.length); - for (var i = 0; i < postIndices.length && postIndices[i] < data.bookmark; i += 1) { - bookmark -= 1; + if (bookmark === data.bookmark) { + return next(); + } + + user.getSettings(data.uid, function (err, settings) { + if (err) { + return next(err); } - if (parseInt(bookmark, 10) !== parseInt(data.bookmark, 10)) { - Topics.setUserBookmark(tid, data.uid, bookmark, next); - } else { - next(); + if (settings.topicPostSort === 'most_votes') { + return next(); } + + Topics.setUserBookmark(tid, data.uid, bookmark, next); }); }, next); }, diff --git a/src/topics/create.js b/src/topics/create.js index bc1091bf52..d8108beb70 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -209,18 +209,17 @@ module.exports = function (Topics) { var uid = data.uid; var content = data.content; var postData; - var cid; async.waterfall([ function (next) { Topics.getTopicField(tid, 'cid', next); }, - function (_cid, next) { - cid = _cid; + function (cid, next) { + data.cid = cid; async.parallel({ topicData: async.apply(Topics.getTopicData, tid), canReply: async.apply(privileges.topics.can, 'topics:reply', tid, uid), - isAdminOrMod: async.apply(privileges.categories.isAdminOrMod, cid, uid), + isAdminOrMod: async.apply(privileges.categories.isAdminOrMod, data.cid, uid), }, next); }, function (results, next) { @@ -243,7 +242,7 @@ module.exports = function (Topics) { guestHandleValid(data, next); }, function (next) { - user.isReadyToPost(uid, cid, next); + user.isReadyToPost(uid, data.cid, next); }, function (next) { plugins.fireHook('filter:topic.reply', data, next); @@ -284,7 +283,7 @@ module.exports = function (Topics) { } Topics.notifyFollowers(postData, uid); - analytics.increment(['posts', 'posts:byCid:' + cid]); + analytics.increment(['posts', 'posts:byCid:' + data.cid]); plugins.fireHook('action:topic.reply', { post: _.clone(postData) }); next(null, postData); diff --git a/src/upgrade.js b/src/upgrade.js index f30a5f43d4..89541ec9f2 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -33,7 +33,13 @@ Upgrade.getAll = function (callback) { versionA = path.dirname(a).split('/').pop(); versionB = path.dirname(b).split('/').pop(); - return semver.compare(versionA, versionB); + var semverCompare = semver.compare(versionA, versionB); + if (semverCompare) { + return semverCompare; + } + var timestampA = require(a).timestamp; + var timestampB = require(b).timestamp; + return timestampA - timestampB; })); }, async.apply(Upgrade.appendPluginScripts), diff --git a/src/upgrades/1.7.6/notification_types.js b/src/upgrades/1.7.6/notification_types.js new file mode 100644 index 0000000000..d8d7636b9c --- /dev/null +++ b/src/upgrades/1.7.6/notification_types.js @@ -0,0 +1,26 @@ +'use strict'; + +var async = require('async'); +var db = require('../../database'); + +module.exports = { + name: 'Add default settings for notification delivery types', + timestamp: Date.UTC(2018, 1, 14), + method: function (callback) { + async.waterfall([ + function (next) { + db.getObject('config', next); + }, + function (config, next) { + db.setObject('config', { + notificationType_upvote: config.notificationType_upvote || 'notification', + 'notificationType_new-topic': config['notificationType_new-topic'] || 'notification', + 'notificationType_new-reply': config['notificationType_new-reply'] || config.sendPostNotifications || 'notification', + notificationType_follow: config.notificationType_follow || 'notification', + 'notificationType_new-chat': config['notificationType_new-chat'] || config.sendChatNotifications || 'notification', + 'notificationType_group-invite': config['notificationType_group-invite'] || 'notification', + }, next); + }, + ], callback); + }, +}; diff --git a/src/user/delete.js b/src/user/delete.js index 92fd8f27b9..0f1ad61047 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -7,6 +7,7 @@ var db = require('../database'); var posts = require('../posts'); var topics = require('../topics'); var groups = require('../groups'); +var messaging = require('../messaging'); var plugins = require('../plugins'); var batch = require('../batch'); @@ -173,12 +174,9 @@ module.exports = function (User) { var userKeys = roomIds.map(function (roomId) { return 'uid:' + uid + ':chat:room:' + roomId + ':mids'; }); - var roomKeys = roomIds.map(function (roomId) { - return 'chat:room:' + roomId + ':uids'; - }); async.parallel([ - async.apply(db.sortedSetsRemove, roomKeys, uid), + async.apply(messaging.leaveRooms, uid, roomIds), async.apply(db.deleteAll, userKeys), ], next); }, diff --git a/src/user/email.js b/src/user/email.js index 9c61211d9a..598536ad29 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -126,14 +126,24 @@ UserEmail.sendValidationEmail = function (uid, options, callback) { }; UserEmail.confirm = function (code, callback) { + var confirmObj; async.waterfall([ function (next) { db.getObject('confirm:' + code, next); }, - function (confirmObj, next) { + function (_confirmObj, next) { + confirmObj = _confirmObj; if (!confirmObj || !confirmObj.uid || !confirmObj.email) { return next(new Error('[[error:invalid-data]]')); } + + user.getUserField(confirmObj.uid, 'email', next); + }, + function (currentEmail, next) { + if (!currentEmail || currentEmail.toLowerCase() !== confirmObj.email) { + return next(new Error('[[error:invalid-email]]')); + } + async.series([ async.apply(user.setUserField, confirmObj.uid, 'email:confirmed', 1), async.apply(db.delete, 'confirm:' + code), diff --git a/src/user/settings.js b/src/user/settings.js index df5ed93d71..4268db7515 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -70,6 +70,7 @@ module.exports = function (User) { settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : defaultTopicsPerPage, defaultTopicsPerPage); settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : defaultPostsPerPage, defaultPostsPerPage); settings.userLang = settings.userLang || meta.config.defaultLang || 'en-GB'; + settings.acpLang = settings.acpLang || settings.userLang; settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest'); settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest'); settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1; @@ -80,6 +81,12 @@ module.exports = function (User) { settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1; settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default'; settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1; + settings.notificationType_upvote = getSetting(settings, 'notificationType_upvote', 'notification'); + settings['notificationType_new-topic'] = getSetting(settings, 'notificationType_new-topic', 'notification'); + settings['notificationType_new-reply'] = getSetting(settings, 'notificationType_new-reply', 'notification'); + settings.notificationType_follow = getSetting(settings, 'notificationType_follow', 'notification'); + settings['notificationType_new-chat'] = getSetting(settings, 'notificationType_new-chat', 'notification'); + settings['notificationType_group-invite'] = getSetting(settings, 'notificationType_group-invite', 'notification'); next(null, settings); }, ], callback); @@ -118,6 +125,7 @@ module.exports = function (User) { topicsPerPage: Math.min(data.topicsPerPage, parseInt(maxTopicsPerPage, 10) || 20), postsPerPage: Math.min(data.postsPerPage, parseInt(maxPostsPerPage, 10) || 20), userLang: data.userLang || meta.config.defaultLang, + acpLang: data.acpLang || meta.config.defaultLang, followTopicsOnCreate: data.followTopicsOnCreate, followTopicsOnReply: data.followTopicsOnReply, restrictChat: data.restrictChat, diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl index 7be444f3d1..1ba3477a07 100644 --- a/src/views/admin/general/navigation.tpl +++ b/src/views/admin/general/navigation.tpl @@ -91,15 +91,6 @@ - [[admin/general/navigation:installed-plugins-required]] - -
- -
- diff --git a/src/views/admin/manage/categories.tpl b/src/views/admin/manage/categories.tpl index 22388d01de..ff46ed5fc9 100644 --- a/src/views/admin/manage/categories.tpl +++ b/src/views/admin/manage/categories.tpl @@ -1,3 +1,5 @@ + +