Merge remote-tracking branch 'origin/master' into develop

v1.18.x
Julian Lam 7 years ago
commit 1ce448f2a1

@ -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"
}

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

@ -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",

@ -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.",

@ -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ů”)"
}

@ -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 <a href=\"%1\">zde</a>.",

@ -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)"
}

@ -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 <strong></strong>",
"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í…"
}

@ -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)"
}

@ -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)",

@ -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"
}

@ -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.",

@ -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",

@ -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 &quot;%1&quot;",
"tag": "Témata označená &quot;%1&quot;",
"register": "Zaregistrovat účet",
"registration-complete": "Registrace dokončena",
"login": "Přihlásit se ke svému účtu",

@ -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. <br/>Zobrazit téma v nepřečtených, není-li tato kategorie ignorována",
"ignoring.description": "Neupozorňovat na nové odpovědi.<br/>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",

@ -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",

@ -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"
}

@ -14,5 +14,6 @@
"alerts.applied-success": "Blacklist Applied",
"analytics.blacklist-hourly": "<strong>Figure 1</strong> &ndash; Blacklist hits per hour",
"analytics.blacklist-daily": "<strong>Figure 2</strong> &ndash; Blacklist hits per day"
"analytics.blacklist-daily": "<strong>Figure 2</strong> &ndash; Blacklist hits per day",
"ip-banned": "IP banned"
}

@ -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"
}

@ -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).",

@ -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.",

@ -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",

@ -1,14 +1,14 @@
{
"figure-x": "Figure %1",
"error-events-per-day": "<code>%1</code> 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 پاک شدند"
}

@ -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": "پاک کردن رویداد ها"
}

@ -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": "گزارش ها پاک شدند"
}

@ -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 <code>&lt;head&gt;</code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-header\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",
"custom-header.enable": "Enable Custom Header",

@ -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": "هنگامی که شما پیام چتی دریافت می کنید",

@ -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": "Выбрать"
}

@ -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": "Скрыть предпросмотр",

@ -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 &quot;%1&quot;",
"tag": "Темы помеченные как &quot;%1&quot;",
"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",

@ -52,7 +52,7 @@
"not-watching.description": "Не уведомлять меня о новых ответах.<br/>Показать тему в непрочитанных, если категория мной не игнорируется.",
"ignoring.description": "Не уведомлять меня о новых ответах.<br/>Не отображать тему в непрочитанных.",
"thread_tools.title": "Настройки темы",
"thread_tools.markAsUnreadForAll": "Mark Unread For All",
"thread_tools.markAsUnreadForAll": "Выделить непрочитанные для всех",
"thread_tools.pin": "Прикрепить тему",
"thread_tools.unpin": "Открепить тему",
"thread_tools.lock": "Закрыть тему",
@ -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": "Отменить",

@ -10,6 +10,6 @@
"all-topics": "Все темы",
"new-topics": "Новые темы",
"watched-topics": "Подписанные темы",
"unreplied-topics": "Unreplied Topics",
"multiple-categories-selected": "Multiple Selected"
"unreplied-topics": "Неотвеченные темы",
"multiple-categories-selected": "Выбрано несколько"
}

@ -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": "Недавно заблокированы",

@ -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 () {

@ -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',
});
}

@ -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 {

@ -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,

@ -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,8 +37,11 @@ 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';
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 {
@ -49,6 +49,7 @@ define('forum/topic/merge', function () {
}
checkButtonEnable();
showTopicsSelected();
});
ev.preventDefault();
ev.stopPropagation();
return false;

@ -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) {
app.parseAndTranslate('partials/topic/post-menu-list', data, function (html) {
dropdownMenu.html(html);
$(window).trigger('action:post.tools.load');
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));
});

@ -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;
}
}

@ -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;

@ -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',

@ -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;

@ -41,11 +41,18 @@ function installAll() {
} catch (e) {
// ignore
}
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;

@ -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;
}

@ -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,

@ -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),

@ -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;

@ -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');
}
};

@ -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);
},

@ -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));

@ -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) {

@ -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);
};

@ -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);
};

@ -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();

@ -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',
},
};

@ -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 }),

@ -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';
}
}
}

@ -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) {

@ -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(' ');
}
};

@ -82,7 +82,6 @@ module.exports = function (Plugins) {
var hookList = Plugins.loadedHooks[hook];
var hookType = hook.split(':')[0];
try {
switch (hookType) {
case 'filter':
fireFilterHook(hook, hookList, params, callback);
@ -95,11 +94,9 @@ module.exports = function (Plugins) {
break;
default:
winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
callback();
break;
}
} catch (err) {
callback(err);
}
};
function fireFilterHook(hook, hookList, params, callback) {

@ -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(),
};

@ -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);
};

@ -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,

@ -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);

@ -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,

@ -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) {
var bookmark = data.bookmark;
bookmark = Math.min(bookmark, maxIndex);
postIndices.forEach(function (i) {
if (i < data.bookmark) {
bookmark -= 1;
}
});
// make sure the bookmark is valid if we removed the last post
bookmark = Math.min(bookmark, maxIndex - pids.length);
if (bookmark === data.bookmark) {
return next();
}
user.getSettings(data.uid, function (err, settings) {
if (err) {
return next(err);
}
var bookmark = data.bookmark;
bookmark = bookmark < maxIndex ? bookmark : maxIndex;
for (var i = 0; i < postIndices.length && postIndices[i] < data.bookmark; i += 1) {
bookmark -= 1;
if (settings.topicPostSort === 'most_votes') {
return next();
}
if (parseInt(bookmark, 10) !== parseInt(data.bookmark, 10)) {
Topics.setUserBookmark(tid, data.uid, bookmark, next);
} else {
next();
}
});
}, next);
},

@ -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);

@ -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),

@ -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);
},
};

@ -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);
},

@ -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),

@ -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,

@ -91,15 +91,6 @@
</label>
</div>
<strong>[[admin/general/navigation:installed-plugins-required]]</strong>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:searchInstalled" <!-- IF enabled.properties.searchInstalled -->checked<!-- ENDIF enabled.properties.searchInstalled -->/>
<span class="mdl-switch__label"><strong>[[admin/general/navigation:search-plugin]]</strong></span>
</label>
</div>
<button class="btn btn-danger delete">[[admin/general/navigation:btn.delete]]</button>
<!-- IF enabled.enabled -->
<button class="btn btn-warning toggle">[[admin/general/navigation:btn.disable]]</button>

@ -1,3 +1,5 @@
<button id="collapse-all" class="btn btn-default">[[admin/manage/categories:collapse-all]]</button> <button id="expand-all" class="btn btn-default">[[admin/manage/categories:expand-all]]</button>
<hr/>
<div class="categories"></div>
<button data-action="create" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">

@ -232,7 +232,6 @@
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/user:default-user-settings]]</div>
<div class="col-sm-10 col-xs-12">
<form>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" data-field="showemail">
@ -292,6 +291,85 @@
</label>
</div>
<label>[[admin/settings/user:default-notification-settings]]</label>
<div class="row">
<div class="form-group col-xs-7">
<label>[[notifications:notificationType_upvote]]</label>
</div>
<div class="form-group col-xs-5">
<select class="form-control" data-field="notificationType_upvote">
<option value="none">[[notifications:none]]</option>
<option value="notification">[[notifications:notification_only]]</option>
<option value="email">[[notifications:email_only]]</option>
<option value="notificationemail">[[notifications:notification_and_email]]</option>
</select>
</div>
</div>
<div class="row">
<div class="form-group col-xs-7">
<label>[[notifications:notificationType_new-topic]]</label>
</div>
<div class="form-group col-xs-5">
<select class="form-control" data-field="notificationType_new-topic">
<option value="none">[[notifications:none]]</option>
<option value="notification">[[notifications:notification_only]]</option>
<option value="email">[[notifications:email_only]]</option>
<option value="notificationemail">[[notifications:notification_and_email]]</option>
</select>
</div>
</div>
<div class="row">
<div class="form-group col-xs-7">
<label>[[notifications:notificationType_new-reply]]</label>
</div>
<div class="form-group col-xs-5">
<select class="form-control" data-field="notificationType_new-reply">
<option value="none">[[notifications:none]]</option>
<option value="notification">[[notifications:notification_only]]</option>
<option value="email">[[notifications:email_only]]</option>
<option value="notificationemail">[[notifications:notification_and_email]]</option>
</select>
</div>
</div>
<div class="row">
<div class="form-group col-xs-7">
<label>[[notifications:notificationType_follow]]</label>
</div>
<div class="form-group col-xs-5">
<select class="form-control" data-field="notificationType_follow">
<option value="none">[[notifications:none]]</option>
<option value="notification">[[notifications:notification_only]]</option>
<option value="email">[[notifications:email_only]]</option>
<option value="notificationemail">[[notifications:notification_and_email]]</option>
</select>
</div>
</div>
<div class="row">
<div class="form-group col-xs-7">
<label>[[notifications:notificationType_new-chat]]</label>
</div>
<div class="form-group col-xs-5">
<select class="form-control" data-field="notificationType_new-chat">
<option value="none">[[notifications:none]]</option>
<option value="notification">[[notifications:notification_only]]</option>
<option value="email">[[notifications:email_only]]</option>
<option value="notificationemail">[[notifications:notification_and_email]]</option>
</select>
</div>
</div>
<div class="row">
<div class="form-group col-xs-7">
<label>[[notifications:notificationType_group-invite]]</label>
</div>
<div class="form-group col-xs-5">
<select class="form-control" data-field="notificationType_group-invite">
<option value="none">[[notifications:none]]</option>
<option value="notification">[[notifications:notification_only]]</option>
<option value="email">[[notifications:email_only]]</option>
<option value="notificationemail">[[notifications:notification_and_email]]</option>
</select>
</div>
</div>
</form>
</div>
</div>

@ -265,6 +265,47 @@ describe('Groups', function () {
});
});
it('should create a hidden group if hidden is 1', function (done) {
Groups.create({
name: 'hidden group',
hidden: '1',
}, function (err) {
assert.ifError(err);
db.isSortedSetMember('groups:visible:memberCount', 'visible group', function (err, isMember) {
assert.ifError(err);
assert(!isMember);
done();
});
});
});
it('should create a visible group if hidden is 0', function (done) {
Groups.create({
name: 'visible group',
hidden: '0',
}, function (err) {
assert.ifError(err);
db.isSortedSetMember('groups:visible:memberCount', 'visible group', function (err, isMember) {
assert.ifError(err);
assert(isMember);
done();
});
});
});
it('should create a visible group if hidden is not passed in', function (done) {
Groups.create({
name: 'visible group 2',
}, function (err) {
assert.ifError(err);
db.isSortedSetMember('groups:visible:memberCount', 'visible group 2', function (err, isMember) {
assert.ifError(err);
assert(isMember);
done();
});
});
});
it('should fail to create group with duplicate group name', function (done) {
Groups.create({ name: 'foo' }, function (err) {
assert(err);

@ -177,10 +177,51 @@ describe('Messaging Library', function () {
Messaging.isUserInRoom(bazUid, roomId, function (err, isUserInRoom) {
assert.ifError(err);
assert.equal(isUserInRoom, false);
Messaging.getRoomData(roomId, function (err, data) {
assert.ifError(err);
assert.equal(data.owner, fooUid);
done();
});
});
});
});
it('should change owner when owner leaves room', function (done) {
socketModules.chats.newRoom({ uid: herpUid }, { touid: fooUid }, function (err, roomId) {
assert.ifError(err);
socketModules.chats.addUserToRoom({ uid: herpUid }, { roomId: roomId, username: 'baz' }, function (err) {
assert.ifError(err);
socketModules.chats.leave({ uid: herpUid }, roomId, function (err) {
assert.ifError(err);
Messaging.getRoomData(roomId, function (err, data) {
assert.ifError(err);
assert.equal(data.owner, fooUid);
done();
});
});
});
});
});
it('should change owner if owner is deleted', function (done) {
User.create({ username: 'deleted_chat_user' }, function (err, sender) {
assert.ifError(err);
User.create({ username: 'receiver' }, function (err, receiver) {
assert.ifError(err);
socketModules.chats.newRoom({ uid: sender }, { touid: receiver }, function (err, roomId) {
assert.ifError(err);
User.deleteAccount(sender, function (err) {
assert.ifError(err);
Messaging.getRoomData(roomId, function (err, data) {
assert.ifError(err);
assert.equal(data.owner, receiver);
done();
});
});
});
});
});
});
it('should fail to remove user from room', function (done) {
socketModules.chats.removeUserFromRoom({ uid: fooUid }, null, function (err) {

@ -357,7 +357,7 @@ describe('Notifications', function () {
setTimeout(function () {
user.notifications.getAll(uid, 'post', function (err, nids) {
assert.ifError(err);
assert.notEqual(nids.indexOf(nid), -1);
assert(nids.includes(nid));
done();
});
}, 1500);

@ -70,22 +70,6 @@ describe('Plugins', function () {
});
});
it('should not crash if there is an exception in a hook', function (done) {
function filterMethod(data, callback) {
var crash;
crash.a = 5;
callback(null, data);
}
plugins.registerHook('test-plugin-crash', { hook: 'filter:test.crashHook', method: filterMethod });
plugins.fireHook('filter:test.crashHook', { foo: 1 }, function (err, data) {
assert(err);
assert.equal(err.message, 'Cannot set property \'a\' of undefined');
done();
});
});
it('should get plugin data from nbbpm', function (done) {
plugins.get('nodebb-plugin-markdown', function (err, data) {
assert.ifError(err);

Loading…
Cancel
Save