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]]
-
-