0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/ar/admin/settings/user.json b/public/language/ar/admin/settings/user.json
index 74e0f2cbe5..bc8a176396 100644
--- a/public/language/ar/admin/settings/user.json
+++ b/public/language/ar/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "السماح بحذف الحساب",
"hide-fullname": "إخفاء الإسم الكامل عن المستخدمين",
"hide-email": "إخفاء البريد الإلكتروني عن المستخدمين",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "القوالب",
"disable-user-skins": "منع المستخدمين من اختيار سمة مخصص",
"account-protection": "حماية الحساب",
diff --git a/public/language/ar/error.json b/public/language/ar/error.json
index 82274cd69a..db0e62c4a1 100644
--- a/public/language/ar/error.json
+++ b/public/language/ar/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "موضوع غير متواجد",
"invalid-pid": "رد غير موجود",
"invalid-uid": "مستخدم غير موجود",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "اسم المستخدم غير مقبول",
"invalid-email": "البريد الاكتروني غير مقبول",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/ar/topic.json b/public/language/ar/topic.json
index 0871e78c11..7a4d167f84 100644
--- a/public/language/ar/topic.json
+++ b/public/language/ar/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "هل أنت متأكد أنك تريد حذف هذه المشاركة؟",
"post_restore_confirm": "هل أنت متأكد أنك تريد استعادة هذه المشاركة؟",
"post_purge_confirm": "هل أنت متأكد أنك تريد تطهير هذه المشاركة؟",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "تحميل الفئات",
"confirm_move": "انقل",
"confirm_fork": "فرع",
diff --git a/public/language/bg/admin/settings/api.json b/public/language/bg/admin/settings/api.json
index ddc03fab20..4cc23fc914 100644
--- a/public/language/bg/admin/settings/api.json
+++ b/public/language/bg/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Кодове",
+ "settings": "Settings",
"lead-text": "На тази страница можете да настроите достъпа до ППИ за писане в NodeBB.",
"intro": "По подразбиране ППИ за писане удостоверява потребителите чрез бисквитката им за сесията, но NodeBB поддържа и удостоверяване чрез метода „Bearer“, използвайки кодовете от тази страница.",
"docs": "Щракнете тук за достъп до пълната документация на ППИ",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "Потребителски ИД",
"uid-help-text": "Посочете потребителски ИД, който да бъде свързан с този код. Ако ИД е 0, това ще се счита за главен код, който може да приема идентичността на всеки от другите потребители чрез параметъра _uid",
"description": "Описание",
diff --git a/public/language/bg/admin/settings/user.json b/public/language/bg/admin/settings/user.json
index 7feafb3897..aaffcdddb1 100644
--- a/public/language/bg/admin/settings/user.json
+++ b/public/language/bg/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Позволяване на изтриването на профила",
"hide-fullname": "Скриване на пълното име от потребителите",
"hide-email": "Скриване на е-пощата от потребителите",
+ "show-fullname-as-displayname": "Показване на цялото име на потребителя, ако е налично",
"themes": "Теми",
"disable-user-skins": "Потребителите да не могат да избират собствен облик",
"account-protection": "Защита на акаунта",
diff --git a/public/language/bg/error.json b/public/language/bg/error.json
index e5503bdfed..adbbe80085 100644
--- a/public/language/bg/error.json
+++ b/public/language/bg/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Грешен идентификатор на тема",
"invalid-pid": "Грешен идентификатор на публикация",
"invalid-uid": "Грешен идентификатор на потребител",
+ "invalid-date": "Трябва да бъде посочена правилна дата",
"invalid-username": "Грешно потребителско име",
"invalid-email": "Грешна е-поща",
"invalid-fullname": "Грешно пълно име",
diff --git a/public/language/bg/topic.json b/public/language/bg/topic.json
index 2c31e0f5d9..ae33515955 100644
--- a/public/language/bg/topic.json
+++ b/public/language/bg/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Наистина ли искате да изтриете тази публикация?",
"post_restore_confirm": "Наистина ли искате да възстановите тази публикация?",
"post_purge_confirm": "Наистина ли искате да изчистите тази публикация?",
+ "pin-modal-expiry": "Дата на давност",
+ "pin-modal-help": "Ако желаете, тук можете да посочите дата на давност за закачените теми. Можете и да оставите полето празно, при което темата ще остане закачена, докато не бъде откачена ръчно.",
"load_categories": "Зареждане на категориите",
"confirm_move": "Преместване",
"confirm_fork": "Разделяне",
diff --git a/public/language/bn/admin/settings/api.json b/public/language/bn/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/bn/admin/settings/api.json
+++ b/public/language/bn/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/bn/admin/settings/user.json b/public/language/bn/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/bn/admin/settings/user.json
+++ b/public/language/bn/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/bn/error.json b/public/language/bn/error.json
index 81e472f845..870e3ea1df 100644
--- a/public/language/bn/error.json
+++ b/public/language/bn/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ভুল টপিক নাম্বার",
"invalid-pid": "ভুল পোস্ট নাম্বার",
"invalid-uid": "ভুল ব্যবহারকারী নাম্বার",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "ভুল ইউজারনেম",
"invalid-email": "ভুল ইমেইল",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/bn/topic.json b/public/language/bn/topic.json
index 078de5e741..f896cdbd24 100644
--- a/public/language/bn/topic.json
+++ b/public/language/bn/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "আপনি নিশ্চিত যে আপনি এই পোষ্টটি মুছে ফেলতে চান ?",
"post_restore_confirm": "আপনি নিশ্চিত যে আপনি এই পোষ্টটি পুনরূূদ্ধার করতে চান ? ",
"post_purge_confirm": "আপনি নিশ্চিত যে আপনি এই পোষ্টটি পার্জ করতে চান ? ",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "ক্যাটাগরী লোড করা হচ্ছে",
"confirm_move": "সরান",
"confirm_fork": "ফর্ক",
diff --git a/public/language/cs/admin/settings/api.json b/public/language/cs/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/cs/admin/settings/api.json
+++ b/public/language/cs/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json
index 1649bb77b4..f63ff2a5aa 100644
--- a/public/language/cs/admin/settings/user.json
+++ b/public/language/cs/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Povolit smazání účtu",
"hide-fullname": "Skrýt jméno před uživateli",
"hide-email": "Skrýt e-mail před uživateli",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Motivy",
"disable-user-skins": "Zabránit uživateli ve výběru vlastního vzhledu",
"account-protection": "Ochrana účtu",
diff --git a/public/language/cs/error.json b/public/language/cs/error.json
index 68131bfc6c..54ac427e51 100644
--- a/public/language/cs/error.json
+++ b/public/language/cs/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Neplatné ID tématu",
"invalid-pid": "Neplatné ID příspěvku",
"invalid-uid": "Neplatné ID uživatele",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Neplatné uživatelské jméno",
"invalid-email": "Neplatný e-mail",
"invalid-fullname": "Neplatný celý název",
diff --git a/public/language/cs/topic.json b/public/language/cs/topic.json
index 14cff97bdf..4a51e68865 100644
--- a/public/language/cs/topic.json
+++ b/public/language/cs/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Jste si jist/a, že chcete odstranit tento příspěvek?",
"post_restore_confirm": "Jste si jist/a, že chcete obnovit tento příspěvek?",
"post_purge_confirm": "Jste si jist/a, že chcete tento příspěvek vyčistit?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Načítání kategorií",
"confirm_move": "Přesunout",
"confirm_fork": "Rozdělit",
diff --git a/public/language/da/admin/settings/api.json b/public/language/da/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/da/admin/settings/api.json
+++ b/public/language/da/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/da/admin/settings/user.json b/public/language/da/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/da/admin/settings/user.json
+++ b/public/language/da/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/da/error.json b/public/language/da/error.json
index 7dc54eb36b..b343475b1b 100644
--- a/public/language/da/error.json
+++ b/public/language/da/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Ugyldig Tråd ID",
"invalid-pid": "Ugyldig Indlæg ID",
"invalid-uid": "Ugyldig Bruger ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Ugyldig Brugernavn",
"invalid-email": "Ugyldig Email",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/da/topic.json b/public/language/da/topic.json
index 7ae6aa1e22..94c5dacfb0 100644
--- a/public/language/da/topic.json
+++ b/public/language/da/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Er du sikker på at du vil slette dette indlæg?",
"post_restore_confirm": "Er du sikker på at du vil gendanne dette indlæg?",
"post_purge_confirm": "Er du sikker på at du vil udradere dette indlæg?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Indlæser kategorier",
"confirm_move": "Flyt",
"confirm_fork": "Fraskil",
diff --git a/public/language/de/admin/settings/api.json b/public/language/de/admin/settings/api.json
index 4dbd045f03..0dce3d8641 100644
--- a/public/language/de/admin/settings/api.json
+++ b/public/language/de/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "Von dieser Seite aus können Sie den Zugriff auf die Schreib-API in NodeBB konfigurieren.",
"intro": "Standardmäßig authentifiziert die write-api Nutzer anhand ihres Sitzungs-Cookies, aber NodeBB unterstützt auch die Bearer-Authentifizierung über Token, die über diese Seite generiert werden.",
"docs": "Klicken Sie hier, um auf die vollständige API-Spezifikation zuzugreifen",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "Nutzer–ID",
"uid-help-text": "Geben Sie eine Nutzer–ID an, die mit diesem Token verknüpft werden soll. Wenn die Benutzer-ID 0 lautet, wird sie als ein master-Token betrachtet, das die Identität anderer Benutzer auf der Grundlage des Parameters _uid annehmen kann.",
"description": "Beschreibung",
diff --git a/public/language/de/admin/settings/user.json b/public/language/de/admin/settings/user.json
index 66dd8d835f..11272dbd85 100644
--- a/public/language/de/admin/settings/user.json
+++ b/public/language/de/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Erlaube löschen des Kontos",
"hide-fullname": "Den 'Kompletten Namen' von Benutzern verstecken",
"hide-email": "Die Email-Adresse von Benutzern verstecken",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Verhindere das Benutzer eigene Skins verwenden",
"account-protection": "Kontosicherheit",
diff --git a/public/language/de/error.json b/public/language/de/error.json
index 7f3a1fc479..a04522f273 100644
--- a/public/language/de/error.json
+++ b/public/language/de/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Ungültige Themen-ID",
"invalid-pid": "Ungültige Beitrags-ID",
"invalid-uid": "Ungültige Benutzer-ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Ungültiger Benutzername",
"invalid-email": "Ungültige E-Mail-Adresse",
"invalid-fullname": "Ungültiger Name",
diff --git a/public/language/de/topic.json b/public/language/de/topic.json
index a28cb572dc..b0cee934e6 100644
--- a/public/language/de/topic.json
+++ b/public/language/de/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Sind Sie sicher, dass Sie diesen Beitrag löschen möchten?",
"post_restore_confirm": "Sind Sie sicher, dass Sie diesen Beitrag wiederherstellen möchten?",
"post_purge_confirm": "Sind Sie sicher, dass Sie diesen Beitrag endgültig löschen möchten?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Kategorien laden",
"confirm_move": "Verschieben",
"confirm_fork": "Aufspalten",
diff --git a/public/language/el/admin/settings/api.json b/public/language/el/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/el/admin/settings/api.json
+++ b/public/language/el/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/el/admin/settings/user.json b/public/language/el/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/el/admin/settings/user.json
+++ b/public/language/el/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/el/error.json b/public/language/el/error.json
index 373bc824d9..4f706777c7 100644
--- a/public/language/el/error.json
+++ b/public/language/el/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Άκυρο ID Θέματος",
"invalid-pid": "Άκυρο ID Δημοσίευσης",
"invalid-uid": "Άκυρο ID Χρήστη",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Άκυρο Όνομα Χρήστη",
"invalid-email": "Άκυρο Email",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/el/topic.json b/public/language/el/topic.json
index 8128c0a405..7f28aeb626 100644
--- a/public/language/el/topic.json
+++ b/public/language/el/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Είσαι σίγουρος/η πως θέλεις να διαγράψεις αυτή την δημοσίευση;",
"post_restore_confirm": "Είσαι σίγουρος/η πως θέλεις να επαναφέρεις αυτή την δημοσίευση;",
"post_purge_confirm": "Είσαι σίγουρος/η πως θέλεις να εκκαθαρίσεις αυτή την δημοσίευση;",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Οι Κατηγορίες Φορτώνουν",
"confirm_move": "Μετακίνηση",
"confirm_fork": "Διαχωρισμός",
diff --git a/public/language/en-GB/admin/settings/api.json b/public/language/en-GB/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/en-GB/admin/settings/api.json
+++ b/public/language/en-GB/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/en-GB/admin/settings/user.json b/public/language/en-GB/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/en-GB/admin/settings/user.json
+++ b/public/language/en-GB/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json
index 206e7bbf26..1b054dee4e 100644
--- a/public/language/en-GB/error.json
+++ b/public/language/en-GB/error.json
@@ -11,6 +11,7 @@
"invalid-tid": "Invalid Topic ID",
"invalid-pid": "Invalid Post ID",
"invalid-uid": "Invalid User ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Invalid Username",
"invalid-email": "Invalid Email",
diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json
index ef7d946bcf..8dc2f22401 100644
--- a/public/language/en-GB/topic.json
+++ b/public/language/en-GB/topic.json
@@ -103,6 +103,9 @@
"post_restore_confirm": "Are you sure you want to restore this post?",
"post_purge_confirm": "Are you sure you want to purge this post?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
+
"load_categories": "Loading Categories",
"confirm_move": "Move",
"confirm_fork": "Fork",
diff --git a/public/language/en-US/admin/settings/api.json b/public/language/en-US/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/en-US/admin/settings/api.json
+++ b/public/language/en-US/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/en-US/admin/settings/user.json b/public/language/en-US/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/en-US/admin/settings/user.json
+++ b/public/language/en-US/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json
index 82ae136078..c50e9c05cd 100644
--- a/public/language/en-US/error.json
+++ b/public/language/en-US/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Invalid Topic ID",
"invalid-pid": "Invalid Post ID",
"invalid-uid": "Invalid User ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Invalid Username",
"invalid-email": "Invalid Email",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/en-US/topic.json b/public/language/en-US/topic.json
index 16dc2d4b75..a38f9433fc 100644
--- a/public/language/en-US/topic.json
+++ b/public/language/en-US/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Are you sure you want to delete this post?",
"post_restore_confirm": "Are you sure you want to restore this post?",
"post_purge_confirm": "Are you sure you want to purge this post?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Loading Categories",
"confirm_move": "Move",
"confirm_fork": "Fork",
diff --git a/public/language/en-x-pirate/admin/settings/api.json b/public/language/en-x-pirate/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/en-x-pirate/admin/settings/api.json
+++ b/public/language/en-x-pirate/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/en-x-pirate/admin/settings/user.json b/public/language/en-x-pirate/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/en-x-pirate/admin/settings/user.json
+++ b/public/language/en-x-pirate/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json
index 82ae136078..c50e9c05cd 100644
--- a/public/language/en-x-pirate/error.json
+++ b/public/language/en-x-pirate/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Invalid Topic ID",
"invalid-pid": "Invalid Post ID",
"invalid-uid": "Invalid User ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Invalid Username",
"invalid-email": "Invalid Email",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/en-x-pirate/topic.json b/public/language/en-x-pirate/topic.json
index 16dc2d4b75..a38f9433fc 100644
--- a/public/language/en-x-pirate/topic.json
+++ b/public/language/en-x-pirate/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Are you sure you want to delete this post?",
"post_restore_confirm": "Are you sure you want to restore this post?",
"post_purge_confirm": "Are you sure you want to purge this post?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Loading Categories",
"confirm_move": "Move",
"confirm_fork": "Fork",
diff --git a/public/language/es/admin/settings/api.json b/public/language/es/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/es/admin/settings/api.json
+++ b/public/language/es/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/es/admin/settings/user.json b/public/language/es/admin/settings/user.json
index 1c589f4899..d460890735 100644
--- a/public/language/es/admin/settings/user.json
+++ b/public/language/es/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Permitir borrar cuenta",
"hide-fullname": "Esconder nombre completo de los usuarios",
"hide-email": "Esconder email de los usuarios",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Plantillas",
"disable-user-skins": "Impedir que los usuarios elijan plantilla",
"account-protection": "Protección de Cuenta",
diff --git a/public/language/es/error.json b/public/language/es/error.json
index 7542b0eea7..342c3a3c10 100644
--- a/public/language/es/error.json
+++ b/public/language/es/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Identificador de tema no válido",
"invalid-pid": "Identificador de publicación no válido",
"invalid-uid": "Identificador de usuario no válido",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nombre de usuario no válido",
"invalid-email": "Correo electrónico no válido",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/es/topic.json b/public/language/es/topic.json
index d0073d0a43..98c6231be0 100644
--- a/public/language/es/topic.json
+++ b/public/language/es/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "¿Estás seguro de que quieres eliminar este mensaje?",
"post_restore_confirm": "¿Estás seguro de que quieres restaurar este mensaje?",
"post_purge_confirm": "¡Estás seguro de que quieres purgar esta publicación?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Cargando categorías",
"confirm_move": "Mover",
"confirm_fork": "Dividir",
diff --git a/public/language/et/admin/settings/api.json b/public/language/et/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/et/admin/settings/api.json
+++ b/public/language/et/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/et/admin/settings/user.json b/public/language/et/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/et/admin/settings/user.json
+++ b/public/language/et/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/et/error.json b/public/language/et/error.json
index 2a9fcf3132..93b5d2f213 100644
--- a/public/language/et/error.json
+++ b/public/language/et/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Vigane teema ID",
"invalid-pid": "Vigane postituse ID",
"invalid-uid": "Vigane kasutaja ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Vigane kasutajanimi",
"invalid-email": "Vigane emaili aadress",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/et/topic.json b/public/language/et/topic.json
index 415dac6e17..7e1a451d1c 100644
--- a/public/language/et/topic.json
+++ b/public/language/et/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Oled kindel, et soovid kustutada selle postituse?",
"post_restore_confirm": "Oled kindel, et soovid taastada antud postituse?",
"post_purge_confirm": "Oled kindel, et soovid täielikult selle teema kustutada?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Laen kategooriaid",
"confirm_move": "Liiguta",
"confirm_fork": "Fork",
diff --git a/public/language/fa-IR/admin/settings/api.json b/public/language/fa-IR/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/fa-IR/admin/settings/api.json
+++ b/public/language/fa-IR/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/fa-IR/admin/settings/post.json b/public/language/fa-IR/admin/settings/post.json
index 27493aafbd..fb0a7ad95e 100644
--- a/public/language/fa-IR/admin/settings/post.json
+++ b/public/language/fa-IR/admin/settings/post.json
@@ -4,7 +4,7 @@
"sorting.oldest-to-newest": "Oldest to Newest",
"sorting.newest-to-oldest": "Newest to Oldest",
"sorting.most-votes": "Most Votes",
- "sorting.most-posts": "Most Posts",
+ "sorting.most-posts": "بیشترین پست",
"sorting.topic-default": "Default Topic Sorting",
"length": "Post Length",
"post-queue": "Post Queue",
diff --git a/public/language/fa-IR/admin/settings/user.json b/public/language/fa-IR/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/fa-IR/admin/settings/user.json
+++ b/public/language/fa-IR/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json
index b2a7b9cf65..b2b5726174 100644
--- a/public/language/fa-IR/error.json
+++ b/public/language/fa-IR/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "شناسه موضوع نامعتبر است.",
"invalid-pid": "شناسه پست نامعتبر است.",
"invalid-uid": "شناسه کاربر نامعتبر است.",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "نام کاربری نامعتبر است.",
"invalid-email": "ایمیل نامعتبر است.",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/fa-IR/global.json b/public/language/fa-IR/global.json
index e5eec7154d..c663b2ec90 100644
--- a/public/language/fa-IR/global.json
+++ b/public/language/fa-IR/global.json
@@ -25,7 +25,7 @@
"pagination.out_of": "%1 از %2",
"pagination.enter_index": "شماره را وارد کنید",
"header.admin": "مدیر",
- "header.categories": "دسته بندیها",
+ "header.categories": "دستهبندیها",
"header.recent": "تازهها",
"header.unread": "نخواندهها",
"header.tags": "برچسبها",
@@ -33,7 +33,7 @@
"header.top": "Top",
"header.users": "کاربران",
"header.groups": "گروهها",
- "header.chats": "چتها",
+ "header.chats": "چت ها",
"header.notifications": "آگاهسازیها",
"header.search": "جستجو",
"header.profile": "نمایه",
@@ -56,13 +56,13 @@
"best": "بهترین",
"votes": "رای ها",
"x-votes": "%1 votes",
- "voters": "Voters",
+ "voters": "رای دهندگان",
"upvoters": "موافقین",
"upvoted": "رای مثبت",
"downvoters": "مخالفین",
"downvoted": "رای منفی",
"views": "بازدیدها",
- "posters": "Posters",
+ "posters": "کاربران",
"reputation": "اعتبار",
"lastpost": "Last post",
"firstpost": "First post",
diff --git a/public/language/fa-IR/modules.json b/public/language/fa-IR/modules.json
index 8f7d873aa9..6548cf766a 100644
--- a/public/language/fa-IR/modules.json
+++ b/public/language/fa-IR/modules.json
@@ -52,7 +52,7 @@
"composer.formatting.italic": "کج",
"composer.formatting.list": "فهرست",
"composer.formatting.strikethrough": "خط خورده",
- "composer.formatting.code": "Code",
+ "composer.formatting.code": "کد",
"composer.formatting.link": "پیوند",
"composer.formatting.picture": "عکس",
"composer.upload-picture": "بارگذاری عکس",
diff --git a/public/language/fa-IR/notifications.json b/public/language/fa-IR/notifications.json
index 86508854c4..8d1290fa11 100644
--- a/public/language/fa-IR/notifications.json
+++ b/public/language/fa-IR/notifications.json
@@ -25,9 +25,9 @@
"upvoted_your_post_in_multiple": "%1و %2 دیگران به پست شما رای مثبت دادن در %3.",
"moved_your_post": "%1 پست شما را به %2 انتقال داده است",
"moved_your_topic": "%2 %1 را منتقل کرده است",
- "user_flagged_post_in": "%1 پست شما را در %2 علامتدار کرده",
- "user_flagged_post_in_dual": "%1 و %2 نشانهگذاری کرده اند پست را در %3",
- "user_flagged_post_in_multiple": "%1 و %2 نفر دیگر این پست را نشانهگذاری کرده در %3",
+ "user_flagged_post_in": "%1 پستی را در %2 گزارش کرده",
+ "user_flagged_post_in_dual": "%1 و %2 پستی را در %3 گزارش کرده اند",
+ "user_flagged_post_in_multiple": "%1 و %2 نفر دیگر این پست را در %3 گزارش کرده اند",
"user_flagged_user": "%1کاربری را برای بررسی گزارش کرد (%2)",
"user_flagged_user_dual": "%1و %2کاربری را برای بررسی گزارش کردند (%3)",
"user_flagged_user_multiple": "%1و %2 دیگران کاربری را برای بررسی گزارش کردند (%3)",
diff --git a/public/language/fa-IR/pages.json b/public/language/fa-IR/pages.json
index 8ff3ecc799..b49df3b326 100644
--- a/public/language/fa-IR/pages.json
+++ b/public/language/fa-IR/pages.json
@@ -19,7 +19,7 @@
"users/sort-posts": "کاربران با بیشترین پست",
"users/sort-reputation": "کاربران دارای بیشترین اعتبار",
"users/banned": "کاربران مسدود شده",
- "users/most-flags": "بیشترین کاربران پرچم شده",
+ "users/most-flags": "بیشترین کاربران گزارش شده",
"users/search": "جستجوی کاربر",
"notifications": "آگاهسازیها",
"tags": "برچسبها",
@@ -31,7 +31,7 @@
"categories": "دستهبندیها",
"groups": "گروهها",
"group": "%1 گروه",
- "chats": "چتها",
+ "chats": "چت ها",
"chat": "چت با %1",
"flags": "گزارش ها",
"flag-details": "جزئیات گزارش %1",
@@ -43,7 +43,7 @@
"account/following": "کاربرانی که %1 دنبال میکند",
"account/followers": "کاربرانی که %1 را دنبال میکنند",
"account/posts": "پستهای %1",
- "account/latest-posts": "Latest posts made by %1",
+ "account/latest-posts": "آخرین پست های %1",
"account/topics": "موضوع های %1",
"account/groups": "گروههای %1",
"account/watched_categories": "%1's Watched Categories",
@@ -53,10 +53,10 @@
"account/ignored": "تاپیک های نادیده گرفته شده توسط %1",
"account/upvoted": "رای مثبت داده شده به پست ها توسط %1",
"account/downvoted": "رای منفی داده شده به پست ها توسط %1",
- "account/best": "بهترین پست های ارسال شده توسط %1",
+ "account/best": "بهترین پست های %1",
"account/blocks": "Blocked users for %1",
"account/uploads": "Uploads by %1",
- "account/sessions": "Login Sessions",
+ "account/sessions": "Session های ورود",
"confirm": "ایمیل تایید شد",
"maintenance.text": "%1 در حال حاضر تحت تعمیر و نگهدارییست. لطفا زمان دیگری مراجعه کنید.",
"maintenance.messageIntro": "علاوه بر این، مدیر این پیام را گذاشته است:",
diff --git a/public/language/fa-IR/tags.json b/public/language/fa-IR/tags.json
index fdd0d9758c..30485a0ba8 100644
--- a/public/language/fa-IR/tags.json
+++ b/public/language/fa-IR/tags.json
@@ -4,5 +4,5 @@
"enter_tags_here": "برچسبها را اینجا وارد کنید، هر کدام بین %1 و %2 کاراکتر.",
"enter_tags_here_short": "برچسبها را وارد کنید...",
"no_tags": "هنوز برچسبی وجود ندارد.",
- "select_tags": "Select Tags"
+ "select_tags": "انتخاب تگ ها"
}
\ No newline at end of file
diff --git a/public/language/fa-IR/topic.json b/public/language/fa-IR/topic.json
index 5dd9b7fd32..57a6712040 100644
--- a/public/language/fa-IR/topic.json
+++ b/public/language/fa-IR/topic.json
@@ -16,7 +16,7 @@
"last_reply_time": "آخرین پاسخ",
"reply-as-topic": "پاسخ به موضوع",
"guest-login-reply": "وارد شوید تا پست بفرستید",
- "login-to-view": "🔒 Log in to view",
+ "login-to-view": "🔒 برای مشاهده وارد شوید ",
"edit": "ویرایش",
"delete": "حذف",
"purge": "پاک کردن",
@@ -60,7 +60,7 @@
"not-watching.description": "به من پس از ارسال هر پاسخی جدیدی اطلاع نده.0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/fi/admin/settings/user.json b/public/language/fi/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/fi/admin/settings/user.json
+++ b/public/language/fi/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/fi/error.json b/public/language/fi/error.json
index a2195c5d75..8befaea4bd 100644
--- a/public/language/fi/error.json
+++ b/public/language/fi/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Virheellinen aiheen ID",
"invalid-pid": "Virheellinen viestin ID",
"invalid-uid": "Virheellinen käyttäjän ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Virheellinen käyttäjänimi",
"invalid-email": "Virheellinen sähköpostiosoite",
"invalid-fullname": "Kokonimi on virheellinen",
diff --git a/public/language/fi/topic.json b/public/language/fi/topic.json
index e27966e041..e3508fef34 100644
--- a/public/language/fi/topic.json
+++ b/public/language/fi/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Haluatko varmasti poistaa tämän viestin?",
"post_restore_confirm": "Haluatko varmasti palauttaa tämän viestin?",
"post_purge_confirm": "Oletko varma, että haluat poistaa pysyvästi tämän viestin?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Ladataan aihealueita",
"confirm_move": "Siirrä",
"confirm_fork": "Haaroita",
diff --git a/public/language/fr/admin/settings/api.json b/public/language/fr/admin/settings/api.json
index ed9b9914ef..85a6230fd5 100644
--- a/public/language/fr/admin/settings/api.json
+++ b/public/language/fr/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "À partir de cette page, vous pouvez configurer l'accès à l'API d'écriture dans NodeBB.",
"intro": "Par défaut, l'API d'écriture authentifie les utilisateurs en fonction de leur cookie de session, mais NodeBB prend également en charge l'authentification du porteur via des tokens générés via cette page.",
"docs": "Cliquez ici pour accéder à la spécification complète de l'API",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "ID Utilisateur",
"uid-help-text": "Spécifiez un ID utilisateur à associer à ce token. Si l'ID utilisateur est 0, il sera considéré comme un token maître, qui peut prendre l'identité d'autres utilisateurs en fonction du paramètre _uid",
"description": "Description",
diff --git a/public/language/fr/admin/settings/email.json b/public/language/fr/admin/settings/email.json
index c36b86ea7b..b380066085 100644
--- a/public/language/fr/admin/settings/email.json
+++ b/public/language/fr/admin/settings/email.json
@@ -36,6 +36,6 @@
"subscriptions.disable": "Désactiver les actualités du forum ",
"subscriptions.hour": "Heure d'envoi",
"subscriptions.hour-help": "Veuillez entrer un nombre représentant l'heure à laquelle envoyer les emails de résumé (c'est à dire 0 pour minuit, 17 pour 5:00 pm). Gardez à l'esprit qu'il s'agit de l'heure du serveur, et peut ne pas correspondre à votre heure locale.
L'heure du serveur est :
Le prochain mail de resumé sera envoyé à ",
- "notifications.settings": "Email notification settings",
- "notifications.remove-images": "Remove images from email notifications"
+ "notifications.settings": "Paramètres de notification par e-mail",
+ "notifications.remove-images": "Supprimer les images des notifications par e-mail"
}
\ No newline at end of file
diff --git a/public/language/fr/admin/settings/guest.json b/public/language/fr/admin/settings/guest.json
index 0cde9b5f3a..8ddbeba3a4 100644
--- a/public/language/fr/admin/settings/guest.json
+++ b/public/language/fr/admin/settings/guest.json
@@ -1,7 +1,7 @@
{
- "settings": "Settings",
+ "settings": "Paramètres",
"handles.enabled": "Autoriser les invités à poster",
"handles.enabled-help": "Cette option affiche un nouveau champ qui permet aux invités de choisir un nom qui sera associé à chaque message qu'ils rédigent. Si désactivé, il seront simplement nommés \"Invité\".",
"topic-views.enabled": "Autoriser les invités à augmenter le nombre de consultations de sujets",
- "reply-notifications.enabled": "Allow guests to generate reply notifications"
+ "reply-notifications.enabled": "Autoriser les invités à générer des notifications de réponse"
}
\ No newline at end of file
diff --git a/public/language/fr/admin/settings/user.json b/public/language/fr/admin/settings/user.json
index 2f8e608045..57d2126740 100644
--- a/public/language/fr/admin/settings/user.json
+++ b/public/language/fr/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Autoriser la suppression des comptes",
"hide-fullname": "Masquer le nom complet aux utilisateurs",
"hide-email": "Masquer les emails aux utilisateurs",
+ "show-fullname-as-displayname": "Afficher le nom complet de l'utilisateur en tant que nom d'affichage si disponible",
"themes": "Thèmes",
"disable-user-skins": "Empêcher les utilisateurs de choisir un skin personnalisé",
"account-protection": "Protection du compte",
diff --git a/public/language/fr/error.json b/public/language/fr/error.json
index d749f521a5..0dfa71bab6 100644
--- a/public/language/fr/error.json
+++ b/public/language/fr/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID de sujet invalide",
"invalid-pid": "ID de message invalide",
"invalid-uid": "ID d'utilisateur invalide",
+ "invalid-date": "Une date valide doit être fournie",
"invalid-username": "Nom d'utilisateur invalide",
"invalid-email": "Email invalide",
"invalid-fullname": "Nom erroné ",
diff --git a/public/language/fr/flags.json b/public/language/fr/flags.json
index 238191de50..dba9270841 100644
--- a/public/language/fr/flags.json
+++ b/public/language/fr/flags.json
@@ -67,7 +67,7 @@
"sort-upvotes": "Demandes positives",
"sort-replies": "Réponses",
- "modal-title": "Report Content",
+ "modal-title": "Contenu du rapport",
"modal-body": "Veuillez saisir le motif de votre signalement pour %1 %2 et sélectionner le bouton ci-dessous le plus approprié.",
"modal-reason-spam": "Spam",
"modal-reason-offensive": "Choquant",
diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json
index 4366bc070d..399972e7a7 100644
--- a/public/language/fr/topic.json
+++ b/public/language/fr/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Êtes-vous sûr de bien vouloir supprimer ce message ?",
"post_restore_confirm": "Êtes-vous sûr de bien vouloir restaurer ce message ?",
"post_purge_confirm": "Êtes-vous sûr de bien vouloir supprimer définitivement ce sujet ?",
+ "pin-modal-expiry": "Date d'expiration",
+ "pin-modal-help": "Vous pouvez éventuellement définir une date d'expiration pour le(s) sujet(s) épinglé(s) ici. Vous pouvez également laisser ce champ vide pour que le sujet reste épinglé jusqu'à ce qu'il soit supprimé manuellement.",
"load_categories": "Chargement des catégories en cours",
"confirm_move": "Déplacer",
"confirm_fork": "Scinder",
diff --git a/public/language/gl/admin/settings/api.json b/public/language/gl/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/gl/admin/settings/api.json
+++ b/public/language/gl/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/gl/admin/settings/user.json b/public/language/gl/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/gl/admin/settings/user.json
+++ b/public/language/gl/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/gl/error.json b/public/language/gl/error.json
index 900c406a45..3377d8805b 100644
--- a/public/language/gl/error.json
+++ b/public/language/gl/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Identificador de Tema Inválido",
"invalid-pid": "Identificador de Publicación Inválido",
"invalid-uid": "Identificador de Usuario Inválido",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nome de Usuario Inválido",
"invalid-email": "Enderezo electrónico inválido",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/gl/topic.json b/public/language/gl/topic.json
index ef9e356214..91bacd38cb 100644
--- a/public/language/gl/topic.json
+++ b/public/language/gl/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Estás seguro de que desexas eliminar esta publicación?",
"post_restore_confirm": "Estás seguro de que desexas restaurar esta publicación?",
"post_purge_confirm": "Estás seguro de que desexas purgar esta publicación??",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Cargando categorías",
"confirm_move": "Mover",
"confirm_fork": "Dividir",
diff --git a/public/language/he/admin/dashboard.json b/public/language/he/admin/dashboard.json
index 8efdffd1b1..37ef12c2de 100644
--- a/public/language/he/admin/dashboard.json
+++ b/public/language/he/admin/dashboard.json
@@ -24,9 +24,9 @@
"updates": "עדכונים",
"running-version": "הפורום מעודכן לגרסה %1",
- "keep-updated": "תוודא תמיד שהפורום שלך עדכני, בכדי שעדכוני אבטחה ותיקוני באגים יהיו מעודכנים.",
+ "keep-updated": "לעדכוני אבטחה מעודכנים ותיקוני באגים, וודא שהפורום שלך עדכני לגרסה האחרונה.",
"up-to-date": "אתה מעודכן
",
- "upgrade-available": "גרסה חדשה (v%1) שוחרר. שקול האם לעדכן את הפורום שלך.
",
+ "upgrade-available": "גרסה חדשה (v%1) שוחררה. שקול האם לעדכן את הפורום שלך.
",
"prerelease-upgrade-available": "זוהי גירסת קדם-הפצה מיושנת של NodeBB. גרסה חדשה (v%1) שוחרר. שקול האם לעדכן את ה-NodeBB שלך.
",
"prerelease-warning": "זוהי גירסת קדם-הפצה של NodeBB. באגים בלתי צפויים עלולים להתרחש.
",
"running-in-development": "הפורום פועל במצב פיתוח. הפורום עשוי להיות חשוף לפגיעות פוטנציאליות; פנה אל מנהל המערכת שלך.",
diff --git a/public/language/he/admin/manage/groups.json b/public/language/he/admin/manage/groups.json
index 5c26bff017..dfa8ca8722 100644
--- a/public/language/he/admin/manage/groups.json
+++ b/public/language/he/admin/manage/groups.json
@@ -1,44 +1,44 @@
{
"name": "שם קבוצה",
- "badge": "Badge",
- "properties": "Properties",
- "description": "תאור קבוצה",
+ "badge": "תגית",
+ "properties": "נתוני קבוצה",
+ "description": "תיאור קבוצה",
"member-count": "מספר חברים",
- "system": "System",
- "hidden": "Hidden",
- "private": "Private",
+ "system": "מערכת",
+ "hidden": "מוסתר",
+ "private": "פרטי",
"edit": "ערוך",
- "delete": "Delete",
- "privileges": "Privileges",
- "download-csv": "CSV",
+ "delete": "מחק",
+ "privileges": "זכויות",
+ "download-csv": "הורד CSV",
"search-placeholder": "חפש",
"create": "צור קבוצה",
"description-placeholder": "תאור קצר על הקבוצה שלך",
"create-button": "צור",
- "alerts.create-failure": "Uh-OhThere was a problem creating your group. Please try again later!
",
+ "alerts.create-failure": "Uh-Ohיצירת הקבוצה נכשלה. נסה שוב מאוחר יותר!
",
"alerts.confirm-delete": "האם אתה בטוח שאתה רוצה למחוק את הקבוצה?",
"edit.name": "שם",
- "edit.description": "תאור",
- "edit.user-title": "הכותרת של החברים",
- "edit.icon": "אייקון קבוצה",
- "edit.label-color": "Group Label Color",
- "edit.text-color": "Group Text Color",
- "edit.show-badge": "הראה תקציב",
- "edit.private-details": "If enabled, joining of groups requires approval from a group owner.",
- "edit.private-override": "Warning: Private groups is disabled at system level, which overrides this option.",
- "edit.disable-join": "Disable join requests",
- "edit.disable-leave": "Disallow users from leaving the group",
- "edit.hidden": "נסתר",
- "edit.hidden-details": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
+ "edit.description": "תיאור",
+ "edit.user-title": "כותרת חברי הקבוצה",
+ "edit.icon": "סמליל קבוצה",
+ "edit.label-color": "צבע תווית קבוצה",
+ "edit.text-color": "צבע טקסט קבוצה",
+ "edit.show-badge": "הצג תג",
+ "edit.private-details": "אם אפשרות זו מופעלת, הצטרפות לקבוצות ידרוש אישור מבעל הקבוצה.",
+ "edit.private-override": "אזהרה: קבוצות פרטיות מושבתות ברמת המערכת, דבר העוקף אפשרות זו.",
+ "edit.disable-join": "השבת בקשות הצטרפות",
+ "edit.disable-leave": "משתמשים לא יוכלו לעזוב את הקבוצה",
+ "edit.hidden": "מוסתר",
+ "edit.hidden-details": "אם אפשרות זו מופעלת, קבוצה זו לא תימצא ברשימת הקבוצות, יהיה ניתן להזמין משתמשים רק באופן ידני",
"edit.add-user": "הוסף משתמש לקבוצה",
"edit.add-user-search": "חפש משתמשים",
- "edit.members": "רשימת חברים",
- "control-panel": "ממשק ניהול לקבוצות",
+ "edit.members": "רשימת חברי הקבוצה",
+ "control-panel": "ממשק ניהול קבוצות",
"revert": "בטל שינויים",
"edit.no-users-found": "לא נמצאו משתמשים",
- "edit.confirm-remove-user": "האם אתה בטוח שאתה רוצה להסיר את המשתמש הזה?",
+ "edit.confirm-remove-user": "האם אתה בטוח שאתה רוצה להסיר משתמש זה?",
"edit.save-success": "השינויים נשמרו!"
}
\ No newline at end of file
diff --git a/public/language/he/admin/manage/privileges.json b/public/language/he/admin/manage/privileges.json
index 7f32cfafb7..fb7d158d7f 100644
--- a/public/language/he/admin/manage/privileges.json
+++ b/public/language/he/admin/manage/privileges.json
@@ -3,13 +3,13 @@
"admin": "מנהל",
"group-privileges": "הרשאות קבוצתיות",
"user-privileges": "הרשאות משתמש",
- "edit-privileges": "Edit Privileges",
+ "edit-privileges": "עריכת הרשאות",
"chat": "צ'אט",
"upload-images": "העלאת תמונות",
"upload-files": "העלאת קבצים",
"signature": "חתימה",
"ban": "הרחקה",
- "invite": "Invite",
+ "invite": "הזמנות",
"search-content": "חיפוש תוכן",
"search-users": "חיפוש משתמשים",
"search-tags": "חיפוש תגיות",
@@ -17,9 +17,9 @@
"view-tags": "צפייה בתגיות",
"view-groups": "צפייה בקבוצות",
"allow-local-login": "התחברות מקומית",
- "allow-group-creation": "יצרת קבוצות",
- "view-users-info": "צפיה במידע משתמש",
- "find-category": "מציאת הקטגוריה",
+ "allow-group-creation": "יצירת קבוצות",
+ "view-users-info": "צפה במידע משתמש",
+ "find-category": "מציאת קטגוריה",
"access-category": "גישה לקטגוריה",
"access-topics": "גישה לנושאים",
"create-topics": "יצירת נושאים",
@@ -29,26 +29,26 @@
"view-edit-history": "הצגת היסטוריית עריכות",
"delete-posts": "מחיקת פוסטים",
"view_deleted": "הצגת פוסטים מחוקים",
- "upvote-posts": "Upvote Posts",
- "downvote-posts": "Downvote Posts",
+ "upvote-posts": "הצבעה לפוסטים",
+ "downvote-posts": "הצבעה נגד לפוסטים",
"delete-topics": "מחיקת נושא",
- "purge": "Purge",
- "moderate": "Moderate",
+ "purge": "מחיקה לצמיתות",
+ "moderate": "הרשאות מודרטור",
"admin-dashboard": "לוח מחוונים",
"admin-categories": "קטגוריות",
"admin-privileges": "הרשאות",
"admin-users": "משתמשים",
"admin-settings": "הגדרות",
- "alert.confirm-moderate": "Are you sure you wish to grant the moderation privilege to this user group? This group is public, and any users can join at will.",
- "alert.confirm-save": "Please confirm your intention to save these privileges",
- "alert.saved": "Privilege changes saved and applied",
- "alert.confirm-discard": "Are you sure you wish to discard your privilege changes?",
- "alert.discarded": "Privilege changes discarded",
- "alert.confirm-copyToAll": "Are you sure you wish to apply this privilege set to all categories?",
- "alert.confirm-copyToAllGroup": "Are you sure you wish to apply this group's privilege set to all categories?",
- "alert.confirm-copyToChildren": "Are you sure you wish to apply this privilege set to all descendant (child) categories?",
- "alert.confirm-copyToChildrenGroup": "Are you sure you wish to apply this group's privilege set to all descendant (child) categories?",
- "alert.no-undo": "This action cannot be undone.",
- "alert.admin-warning": "Administrators implicitly get all privileges"
+ "alert.confirm-moderate": "האם אתה בטוח שברצונך להעניק הרשאות בינוניות לקבוצת משתמשים זו? הקבוצה היא ציבורית, וכל משתמש יכול להצטרף כרצונו.",
+ "alert.confirm-save": "נא אשר את הגדרת ההרשאות",
+ "alert.saved": "שינויי הרשאות נשמרו והוחלו",
+ "alert.confirm-discard": "האם אתה בטוח שברצונך לבטל את שינויי הרשאות שלך?",
+ "alert.discarded": "שינויי ההרשאות נמחקו",
+ "alert.confirm-copyToAll": "האם אתה בטוח שברצונך להחיל את קבוצת הרשאות זו לכל הקטגוריות?",
+ "alert.confirm-copyToAllGroup": "זהירות!! האם אתה בטוח שברצונך להחיל את הרשאות קבוצה זו לכל הקטגוריות?",
+ "alert.confirm-copyToChildren": "האם אתה בטוח שברצונך להחיל את קבוצת הרשאות זו לכל קטגוריות הצאצאים (ילדים)?",
+ "alert.confirm-copyToChildrenGroup": "האם אתה בטוח שברצונך להחיל את הרשאות קבוצה זו לכל קטגוריות הצאצאים (ילדים)?",
+ "alert.no-undo": "לא ניתן לבטל פעולה זו.",
+ "alert.admin-warning": "מנהלים מקבלים את כל ההרשאות"
}
\ No newline at end of file
diff --git a/public/language/he/admin/menu.json b/public/language/he/admin/menu.json
index cb952484e6..ba334339a0 100644
--- a/public/language/he/admin/menu.json
+++ b/public/language/he/admin/menu.json
@@ -1,8 +1,8 @@
{
- "dashboard": "Dashboard",
+ "dashboard": "לוח מחוונים",
"section-general": "כללי",
- "section-manage": "ניהול",
+ "section-manage": "נהל",
"manage/categories": "קטגוריות",
"manage/privileges": "הרשאות",
"manage/tags": "תגיות",
@@ -17,68 +17,68 @@
"section-settings": "הגדרות",
"settings/general": "כללי",
- "settings/homepage": "Home Page",
- "settings/navigation": "Navigation",
- "settings/reputation": "Reputation & Flags",
+ "settings/homepage": "דף הבית",
+ "settings/navigation": "ניווט",
+ "settings/reputation": "מוניטין ודגלים",
"settings/email": "דוא\"ל",
- "settings/user": "Users",
- "settings/group": "Groups",
- "settings/guest": "אורח",
+ "settings/user": "משתמשים",
+ "settings/group": "קבוצות",
+ "settings/guest": "אורחים",
"settings/uploads": "העלאות",
- "settings/languages": "Languages",
- "settings/post": "Posts",
- "settings/chat": "Chats",
- "settings/pagination": "Pagination",
+ "settings/languages": "שפות",
+ "settings/post": "פוסטים",
+ "settings/chat": "צ'אטים",
+ "settings/pagination": "עימוד",
"settings/tags": "תגיות",
"settings/notifications": "התראות",
- "settings/api": "API Access",
- "settings/sounds": "Sounds",
- "settings/social": "Social",
+ "settings/api": "גישת API",
+ "settings/sounds": "שמע",
+ "settings/social": "חברתי",
"settings/cookies": "עוגיות",
- "settings/web-crawler": "Web Crawler",
+ "settings/web-crawler": "סורק רשת",
"settings/sockets": "Sockets",
"settings/advanced": "מתקדם",
- "settings.page-title": "%1 Settings",
+ "settings.page-title": "%1 הגדרות",
- "section-appearance": "Appearance",
- "appearance/themes": "Themes",
+ "section-appearance": "מראה חיצוני",
+ "appearance/themes": "ערכות נושא",
"appearance/skins": "עיצובים",
- "appearance/customise": "Custom Content (HTML/JS/CSS)",
+ "appearance/customise": "תוכן מותאם אישי (HTML/JS/CSS)",
"section-extend": "Extend",
- "extend/plugins": "Plugins",
- "extend/widgets": "Widgets",
- "extend/rewards": "Rewards",
+ "extend/plugins": "תוספים",
+ "extend/widgets": "וידג'טים",
+ "extend/rewards": "תגמולים",
- "section-social-auth": "Social Authentication",
+ "section-social-auth": "אימות חברתי",
"section-plugins": "תוספים",
"extend/plugins.install": "תוספים מותקנים",
"section-advanced": "מתקדם",
"advanced/database": "מסד נתונים",
- "advanced/events": "Events",
+ "advanced/events": "ארועים",
"advanced/hooks": "Hooks",
"advanced/logs": "Logs",
- "advanced/errors": "Errors",
- "advanced/cache": "Cache",
+ "advanced/errors": "שגיאות",
+ "advanced/cache": "עוגיות",
"development/logger": "Logger",
- "development/info": "Info",
+ "development/info": "מידע",
- "rebuild-and-restart-forum": "Rebuild & Restart Forum",
- "restart-forum": "Restart Forum",
- "logout": "Log out",
- "view-forum": "View Forum",
+ "rebuild-and-restart-forum": "בנה והפעל מחדש את הפורום",
+ "restart-forum": "הפעל מחדש את הפורום",
+ "logout": "התנתק",
+ "view-forum": "צפה בפורום",
- "search.placeholder": "Press "/" to search for settings",
- "search.no-results": "No results...",
- "search.search-forum": "Search the forum for ",
- "search.keep-typing": "Type more to see results...",
- "search.start-typing": "Start typing to see results...",
+ "search.placeholder": "לחץ "/" בכדי לחפש הגדרה",
+ "search.no-results": "אין תוצאות...",
+ "search.search-forum": "חפש בפורום ",
+ "search.keep-typing": "הקלד עוד על מנת למצוא תוצאות...",
+ "search.start-typing": "התחל להקליד על מנת לראות תוצאות...",
- "connection-lost": "Connection to %1 has been lost, attempting to reconnect...",
+ "connection-lost": "החיבור ל-%1 אבד, מנסה להתחבר מחדש...",
- "alerts.version": "Running NodeBB v%1",
+ "alerts.version": "מריץ NodeBB v%1",
"alerts.upgrade": "שדרג ל v%1"
}
\ No newline at end of file
diff --git a/public/language/he/admin/settings/api.json b/public/language/he/admin/settings/api.json
index 660080a89c..6cc6f5ea7e 100644
--- a/public/language/he/admin/settings/api.json
+++ b/public/language/he/admin/settings/api.json
@@ -1,12 +1,16 @@
{
- "tokens": "Tokens",
- "lead-text": "From this page you can configure access to the Write API in NodeBB.",
- "intro": "כברירת מחדל, ה- API של כתיבה מאמת משתמשים בהתבסס על קובץ ה-cookie של ההפעלה שלהם, אך NodeBB תומך גם באימות נושא באמצעות אסימונים שנוצרו באמצעות דף זה.",
+ "tokens": "טוקנים - Tokens",
+ "settings": "Settings",
+ "lead-text": "מעמוד זה תוכלו להגדיר גישת כתיבה ל-API ב- NodeBB.",
+ "intro": "כברירת מחדל, ה- API של כתיבה מאמת משתמשים בהתבסס על קובץ ה-cookie של ההפעלה שלהם, אך NodeBB תומך גם באימות נושא באמצעות טוקנים (אישורי אבטחה) שנוצרו באמצעות דף זה.",
"docs": "לחץ כאן כדי לגשת למפרט ה- API המלא",
- "uid": "User ID",
- "uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
+ "uid": "ID משתמש",
+ "uid-help-text": "ציין מזהה משתמש בכדי לשייך לטוקן זה. אם מזהה המשתמש הוא 0, זה ייחשב כטוקןראשי, שיכול לשער את זהותם של משתמשים אחרים על בסיס פרמטר_uid .",
"description": "תיאור",
"no-description": "לא צוין תיאור.",
- "token-on-save": "Token will be generated once form is saved"
+ "token-on-save": "טוקן יוצר לאחר שמירת הטופס"
}
\ No newline at end of file
diff --git a/public/language/he/admin/settings/post.json b/public/language/he/admin/settings/post.json
index 30e5f945df..c5e3c1838b 100644
--- a/public/language/he/admin/settings/post.json
+++ b/public/language/he/admin/settings/post.json
@@ -7,15 +7,15 @@
"sorting.most-posts": "הכי הרבה פוסטים",
"sorting.topic-default": "מיון ברירת מחדל של נושאים",
"length": "אורך פוסט",
- "post-queue": "Post Queue",
- "restrictions": "Posting Restrictions",
- "restrictions-new": "New User Restrictions",
- "restrictions.post-queue": "Enable post queue",
- "restrictions.post-queue-rep-threshold": "Reputation required to bypass post queue",
- "restrictions.groups-exempt-from-post-queue": "Select groups that should be exempt from the post queue",
- "restrictions-new.post-queue": "Enable new user restrictions",
- "restrictions.post-queue-help": "Enabling post queue will put the posts of new users in a queue for approval",
- "restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users",
+ "post-queue": "תור פוסטים",
+ "restrictions": "הגבלות רישום",
+ "restrictions-new": "הגבלות משתמש חדש",
+ "restrictions.post-queue": "הפוך תור פוסט לזמין",
+ "restrictions.post-queue-rep-threshold": "מוניטין נדרש כדי לעקוף תור פוסט",
+ "restrictions.groups-exempt-from-post-queue": "בחר קבוצות פטורות מתור פוסטים",
+ "restrictions-new.post-queue": "הפיכת הגבלות משתמש חדשות לזמינות",
+ "restrictions.post-queue-help": "הפעלת תור פוסטים תכניס את ההודעות של משתמשים חדשים לתור לאישור",
+ "restrictions-new.post-queue-help": "הפעלת הגבלות משתמשים חדשים תגדיר הגבלות על פוסטים שנוצרו על-ידי משתמשים חדשים",
"restrictions.seconds-between": "מספר השניות בין פוסטים",
"restrictions.seconds-between-new": "שניות בין פוסטים עבור משתמשים חדשים",
"restrictions.rep-threshold": "סף המוניטין לפני ביטול המגבלות הללו",
@@ -34,8 +34,8 @@
"timestamp.cut-off-help": "Dates & הזמנים יוצגו באופן יחסי (למשל \"לפני 3 שעות\" / \"לפני 5 ימים\"), לפי שעה ושפה מקומית. לאחר זמן זה, יוצג התאריך המקומי עצמו\n\t\t\t\t\t(לדוגמא, 5 בנובמבר 2016, 15:30).
(ברירת מחדל: 30, או חודש אחד). הגדר ל 0 כדי להציג תמיד תאריכים, השאר ריק כדי להציג תמיד זמנים יחסית.",
"timestamp.necro-threshold": "סף ה-Necro (בימים)",
"timestamp.necro-threshold-help": "הודעה תוצג בין פוסטים אם הזמן ביניהם ארוך יותר מסף ה-necro . (ברירת מחדל: 7, או שבוע אחד). כתוב 0 בכדי להפוך ללא זמין.",
- "timestamp.topic-views-interval": "Increment topic views interval (in minutes)",
- "timestamp.topic-views-interval-help": "Topic views will only increment once every X minutes as defined by this setting.",
+ "timestamp.topic-views-interval": "מרווח תצוגות של נושא קבוע (בדקות)",
+ "timestamp.topic-views-interval-help": "תצוגות נושא יגדלו רק פעם ב- X דקות כפי שהוגדרו על-ידי הגדרה זו.",
"teaser": "פוסט טיזר",
"teaser.last-post": "Last – הצג את הפוסט האחרון, כולל הפוסט המקורי, אם אין תגובות",
"teaser.last-reply": "Last – הצג את התשובה האחרונה, או ציין \"ללא תשובות\" אם אין תשובות",
@@ -43,20 +43,20 @@
"unread": "הגדרות \"שלא נקראו\"",
"unread.cutoff": "ימי ניתוק שלא נקראו",
"unread.min-track-last": "פוסטים מינימליים בנושא לפני מעקב אחר קריאה אחרונה",
- "recent": "Recent Settings",
- "recent.max-topics": "Maximum topics on /recent",
- "recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page",
- "signature": "Signature Settings",
- "signature.disable": "Disable signatures",
- "signature.no-links": "Disable links in signatures",
- "signature.no-images": "Disable images in signatures",
- "signature.max-length": "Maximum Signature Length",
- "composer": "Composer Settings",
- "composer-help": "The following settings govern the functionality and/or appearance of the post composer shown\n\t\t\t\tto users when they create new topics, or reply to existing topics.",
- "composer.show-help": "Show \"Help\" tab",
- "composer.enable-plugin-help": "Allow plugins to add content to the help tab",
- "composer.custom-help": "Custom Help Text",
- "ip-tracking": "IP Tracking",
- "ip-tracking.each-post": "Track IP Address for each post",
- "enable-post-history": "Enable Post History"
+ "recent": "הגדרות פוסטים אחרונים",
+ "recent.max-topics": "מקסימום נושאים ב פוסטים אחרונים",
+ "recent.categoryFilter.disable": "הפיכת סינון נושאים ללא זמין בקטגוריות שהתעלמו מהן בדף פוסטים אחרונים",
+ "signature": "הגדרות חתימה",
+ "signature.disable": "השבת חתימות",
+ "signature.no-links": "השבת קישורים בחתימות",
+ "signature.no-images": "השבת תמונות בחתימות",
+ "signature.max-length": "אורך חתימה מרבי",
+ "composer": "הגדרות יצירת פוסט",
+ "composer-help": "ההגדרות הבאות חלות על הפונקציונליות ו/או המראה של יוצר הפוסט המוצג\n\t\t\t\tלמשתמשים בעת יצירת נושאים חדשים, או מענה לנושאים קיימים.",
+ "composer.show-help": "הצג כרטיסיית \"עזרה\"",
+ "composer.enable-plugin-help": "אפשר לתוסיפים להוסיף תוכן ללשונית עזרה",
+ "composer.custom-help": "טקסט עזרה מותאם אישית",
+ "ip-tracking": "IP מעקב",
+ "ip-tracking.each-post": "מעקב אחר כתובת IP על כל הודעה",
+ "enable-post-history": "הפוך היסטוריית פוסטים לזמינה"
}
\ No newline at end of file
diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json
index 4cb3549d87..0557595e9c 100644
--- a/public/language/he/admin/settings/user.json
+++ b/public/language/he/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "אפשר מחיקת חשבונות",
"hide-fullname": "החבא שם מלא ממשתמשים",
"hide-email": "החבא כתובת מייל ממשתמשים",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "ערכות נושא",
"disable-user-skins": "אל תאפשר למשתמשים לבחור ערכת נושא",
"account-protection": "Account Protection",
diff --git a/public/language/he/category.json b/public/language/he/category.json
index 2c5322e81d..9cbca38f84 100644
--- a/public/language/he/category.json
+++ b/public/language/he/category.json
@@ -2,7 +2,7 @@
"category": "קטגוריה",
"subcategories": "קטגוריות משנה",
"new_topic_button": "נושא חדש",
- "guest-login-post": "התחבר כדי לפרסם",
+ "guest-login-post": "התחבר בכדי לפרסם",
"no_topics": "קטגוריה זו ריקה מנושאים.
למה שלא תנסה להוסיף נושא חדש?",
"browsing": "צופים בנושא זה כעת",
"no_replies": "אין תגובות",
@@ -15,8 +15,8 @@
"watching.description": "הצג נושאים בנושאים שלא נקראו ובנושאים אחרונים",
"not-watching.description": "הסתר בנושאים שלא נקראו, הצג בנושאים אחרונים",
"ignoring.description": "הסתר נושאים בנושאים שלא נקראו ובנושאים אחרונים",
- "watching.message": "אתה כעת עוקב אחר עדכונים בקטגוריה זו וכל תת-הקטגוריות",
- "notwatching.message": "אתה כעת לא עוקב מעדכונים בקטגוריה זו וכל תת-הקטגוריות",
- "ignoring.message": "אתה כעת מתעלם מעדכונים בקטגוריה זו וכל תת-הקטגוריות",
- "watched-categories": "קטגוריות נעקבות"
+ "watching.message": "בחרת לעקוב אחר עדכונים בקטגוריה זו וכל תת-הקטגוריות",
+ "notwatching.message": "בחרת לא לעקוב אחר עדכונים בקטגוריה זו וכל תת-הקטגוריות",
+ "ignoring.message": "בחרת להתעלם מעדכונים בקטגוריה זו וכל תת-הקטגוריות",
+ "watched-categories": "קטגוריות במעקב"
}
\ No newline at end of file
diff --git a/public/language/he/error.json b/public/language/he/error.json
index 1e8bd386d3..fa55378caf 100644
--- a/public/language/he/error.json
+++ b/public/language/he/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "זהוי נושא שגוי",
"invalid-pid": "זהוי פוסט שגוי",
"invalid-uid": "זהוי משתמש שגוי",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "שם משתמש שגוי",
"invalid-email": "אימייל שגוי",
"invalid-fullname": "שם מלא לא תקין",
diff --git a/public/language/he/flags.json b/public/language/he/flags.json
index f0d0dc9727..1656b0bbf3 100644
--- a/public/language/he/flags.json
+++ b/public/language/he/flags.json
@@ -43,7 +43,7 @@
"notes": "הערות הסימון",
"add-note": "הוסף הערה",
"no-notes": "אין הערות",
- "delete-note-confirm": "האם אתה בטוח שאתה רוצה למחוק את ההערה הזו?",
+ "delete-note-confirm": "האם אתה בטוח שאתה רוצה למחוק הערה זו?",
"note-added": "נוספה הערה",
"note-deleted": "ההערה נמחקה",
diff --git a/public/language/he/global.json b/public/language/he/global.json
index 420512a741..305f0018b8 100644
--- a/public/language/he/global.json
+++ b/public/language/he/global.json
@@ -82,7 +82,7 @@
"norecenttopics": "אין נושאים מהזמן החרון",
"recentposts": "פוסטים אחרונים",
"recentips": "כתובות IP שהתחברו למערכת לאחרונה",
- "moderator_tools": "כלי מוד",
+ "moderator_tools": "כלי מודרטור",
"online": "מחובר",
"away": "לא נמצא",
"dnd": "נא לא להפריע",
diff --git a/public/language/he/groups.json b/public/language/he/groups.json
index bece06c44a..3b2934fb20 100644
--- a/public/language/he/groups.json
+++ b/public/language/he/groups.json
@@ -1,65 +1,65 @@
{
"groups": "קבוצות",
- "view_group": "צפה בקבוצה",
- "owner": "מנהל הקבוצה",
+ "view_group": "הצג קבוצה",
+ "owner": "מנהל קבוצה",
"new_group": "צור קבוצה חדשה",
- "no_groups_found": "אין קבוצות לצפייה",
+ "no_groups_found": "אין קבוצות להצגה",
"pending.accept": "אשר",
"pending.reject": "דחה",
"pending.accept_all": "אשר הכל",
"pending.reject_all": "דחה הכל",
- "pending.none": "אין משתמשים ממתינים כרגע",
+ "pending.none": "אין משתמשים בהמתנה כרגע",
"invited.none": "אין משתמשים מוזמנים כרגע",
"invited.uninvite": "בטל הזמנה",
"invited.search": "חפש משתמש להזמנה לקבוצה זו",
"invited.notification_title": "הוזמנת להצטרף ל%1",
- "request.notification_title": "בקשת חברות קבוצתית מ%1",
+ "request.notification_title": "בקשת חברות בקבוצה מאת %1",
"request.notification_text": "%1 ביקש להיות חבר ב%2",
"cover-save": "שמור",
"cover-saving": "שומר",
- "details.title": "פרטי הקבוצה",
+ "details.title": "פרטי קבוצה",
"details.members": "רשימת חברי הקבוצה",
"details.pending": "חברי קבוצה הממתינים לאישור",
"details.invited": "חברים מוזמנים",
"details.has_no_posts": "חברי הקבוצה הזו לא העלו אף פוסט.",
"details.latest_posts": "פוסטים אחרונים",
"details.private": "פרטי",
- "details.disableJoinRequests": "בטל בקשות הצטרפות",
+ "details.disableJoinRequests": "השבת בקשות הצטרפות",
"details.disableLeave": "אל תאפשר למשתמשים לעזוב את הקבוצה",
"details.grant": "הענק/בטל בעלות",
"details.kick": "גרש",
"details.kick_confirm": "האם אתה בטוח שאתה רוצה להסיר משתמש זה מהקבוצה?",
"details.add-member": "הוסף משתמש",
- "details.owner_options": "ניהול הקבוצה",
- "details.group_name": "שם הקבוצה",
- "details.member_count": "כמות משתמשים",
+ "details.owner_options": "ניהול קבוצה",
+ "details.group_name": "שם קבוצה",
+ "details.member_count": "מספר חברים",
"details.creation_date": "תאריך יצירה",
"details.description": "תיאור",
- "details.member-post-cids": "קטגוריות שמהם יוצגו פוסטים",
+ "details.member-post-cids": "קטגוריות מהם יוצגו פוסטים",
"details.member-post-cids-help": "הערה: אי בחירת קטגוריה תכלול את כל הקטגוריות. השתמש ב-ctrl ו-shift כדי לבחור אפשרויות מרובות.",
- "details.badge_preview": "תצוגה מקדימה של הסמל",
+ "details.badge_preview": "תצוגה מקדימה של התג",
"details.change_icon": "שנה אייקון",
- "details.change_label_colour": "Change Label Colour",
- "details.change_text_colour": "Change Text Colour",
- "details.badge_text": "טקסט הסמל",
- "details.userTitleEnabled": "הצג סמל",
- "details.private_help": "אם מאופשר, הצטרפות לקבוצות דורשות אישור מבעל הקבוצה",
+ "details.change_label_colour": "שנה צבע תווית",
+ "details.change_text_colour": "שנה צבע טקסט",
+ "details.badge_text": "טקסט תגית",
+ "details.userTitleEnabled": "הצג תגית",
+ "details.private_help": "אם אפשרות זו מופעלת, הצטרפות לקבוצות ידרוש אישור מבעל הקבוצה.",
"details.hidden": "מוסתר",
- "details.hidden_help": "אם מאופשר, הקבוצה לא תופיע ברשימת הקבוצות, ומשתמשים יצטרכו להיות מוזמנים ידנית",
+ "details.hidden_help": "אם אפשרות זו מופעלת, קבוצה זו לא תימצא ברשימת הקבוצות, יהיה ניתן להזמין משתמשים רק באופן ידני",
"details.delete_group": "מחק קבוצה",
- "details.private_system_help": "קבוצות פרטיות מבוטלות על ידי המערכת. אופציה זו אינה עושה דבר.",
+ "details.private_system_help": "קבוצות פרטיות מושבתות ברמת המערכת, אפשרות זו אינה עושה דבר",
"event.updated": "פרטי הקבוצה עודכנו",
"event.deleted": "קבוצת \"%1\" נמחקה",
"membership.accept-invitation": "קבל הזמנה",
- "membership.accept.notification_title": "You are now a member of %1",
+ "membership.accept.notification_title": "אתה עכשיו חבר ב%1",
"membership.invitation-pending": "הזמנה ממתינה",
"membership.join-group": "הצטרף לקבוצה",
"membership.leave-group": "עזוב קבוצה",
- "membership.leave.notification_title": "%1 has left group %2",
+ "membership.leave.notification_title": "%1 עזב את קבוצת %2",
"membership.reject": "דחה",
"new-group.group_name": "שם קבוצה",
"upload-group-cover": "העלה תמונת נושא לקבוצה",
- "bulk-invite-instructions": "הזן רשימה מופרדת בפסיק של משתמשים שתרצה להזמין לקבוצה זו.",
+ "bulk-invite-instructions": "הזן רשימה מופרדת בפסיק של משתמשים אותם תרצה להזמין לקבוצה זו.",
"bulk-invite": "הזמן מספר משתמשים",
- "remove_group_cover_confirm": "האם אתה בטוח שאתה רוצה להסיר את תמונת הקאבר?"
+ "remove_group_cover_confirm": "האם אתה בטוח שאתה רוצה להסיר תמונת נושא?"
}
\ No newline at end of file
diff --git a/public/language/he/modules.json b/public/language/he/modules.json
index 8b6b19cb55..7d73b1702f 100644
--- a/public/language/he/modules.json
+++ b/public/language/he/modules.json
@@ -1,5 +1,5 @@
{
- "chat.chatting_with": "לשוחח עם",
+ "chat.chatting_with": "שוחח עם",
"chat.placeholder": "הקלד את הודעת הצ'אט כאן, לחץ אנטר לשליחה",
"chat.scroll-up-alert": "אתה מסתכל על הודעות ישנות. לחץ כאן למעבר להודעה האחרונה.",
"chat.send": "שלח",
@@ -8,35 +8,35 @@
"chat.user_has_messaged_you": "ל%1 יש הודעה עבורך.",
"chat.see_all": "צפה בכל הצ'אטים",
"chat.mark_all_read": "סמן את כל הצ'אטים כ'נקראו'",
- "chat.no-messages": "אנא בחר נמען על מנת לראות את היסטוריית הצ'אט איתו",
+ "chat.no-messages": "בחר משתמש על מנת לראות את שיחות הצ'אט שלכם",
"chat.no-users-in-room": "אין משתמשים בחדר הזה",
"chat.recent-chats": "צ'אטים אחרונים",
"chat.contacts": "אנשי קשר",
"chat.message-history": "היסטוריית הודעות",
"chat.message-deleted": "ההודעה נמחקה",
- "chat.options": "אפשרויות לשיחה",
- "chat.pop-out": "הוצא את חלון הצ'אט",
+ "chat.options": "אפשרויות צ'אט",
+ "chat.pop-out": "מזער חלונית צ'אט",
"chat.minimize": "צמצם",
"chat.maximize": "הרחב",
"chat.seven_days": "7 ימים",
"chat.thirty_days": "30 ימים",
"chat.three_months": "3 חודשים",
"chat.delete_message_confirm": "האם אתה בטוח שברצונך למחוק הודעה זו?",
- "chat.retrieving-users": "שולף משתמשים",
+ "chat.retrieving-users": "מאחזר משתמשים...",
"chat.manage-room": "נהל חדר צ'אט",
"chat.add-user-help": "חפש משתמשים כאן. כאשר משתמש נבחר, הוא יצורף לצ'אט. המשתמש החדש לא יוכל לראות הודעות שנכתבו לפני הצטרפותו. רק מנהלי החדר () יכולים להסיר משתמשים מהצ'אט.",
- "chat.confirm-chat-with-dnd-user": "משתמש זה שינה את הסטטוס שלו ל 'לא להפריע'. אתה עדיין מעוניין לשוחח איתו?",
- "chat.rename-room": "שנה שם של חדר",
+ "chat.confirm-chat-with-dnd-user": "משתמש זה שינה את הסטטוס שלו ל'לא להפריע'. אתה עדיין מעוניין לשוחח איתו?",
+ "chat.rename-room": "שנה שם חדר",
"chat.rename-placeholder": "הזן את שם החדר שלך כאן",
"chat.rename-help": "שם החדר המוגדר כאן יהיה זמין לכל המשתתפים בחדר.",
"chat.leave": "עזוב שיחה",
"chat.leave-prompt": "האם אתה בטוח שאתה רוצה לעזוב את השיחה הזו?",
- "chat.leave-help": "עזיבת צ'אט זה תסיר אותך מהתכתבות עתידית בצ'אט זה. אם תתווסף מחדש בעתיד, לא תראה כל היסטוריית צ'אט שלפני הצטרפותך מחדש.",
+ "chat.leave-help": "עזיבת שיחה, תסיר אותך מהתכתבות עתידית בצ'אט זה. אם תצטרף מחדש בעתיד, לא תראה את היסטוריית הצ'אט שלפני הצטרפותך מחדש.",
"chat.in-room": "בתוך החדר הזה",
- "chat.kick": "גרש",
+ "chat.kick": "הוצא",
"chat.show-ip": "הצג IP",
"chat.owner": "מנהל החדר",
- "chat.system.user-join": "%1 נרשם לחדר",
+ "chat.system.user-join": "%1 הצטרף לחדר",
"chat.system.user-leave": "%1 יצא מהחדר",
"chat.system.room-rename": "%2 שינה את שם החדר: %1",
"composer.compose": "צור",
@@ -54,16 +54,16 @@
"composer.formatting.strikethrough": "קו פוסל",
"composer.formatting.code": "קוד",
"composer.formatting.link": "לינק",
- "composer.formatting.picture": "תמונה",
+ "composer.formatting.picture": "תמונה מהרשת",
"composer.upload-picture": "העלה תמונה",
"composer.upload-file": "העלה קובץ",
- "composer.zen_mode": "מצב זן",
+ "composer.zen_mode": "מסך מלא",
"composer.select_category": "בחר קטגוריה",
- "composer.textarea.placeholder": "כתוב את תוכן הפוסט כאן. ניתן לגרור לכאן תמונות.",
- "bootbox.ok": "אוקיי",
+ "composer.textarea.placeholder": "כתוב את תוכן הפוסט כאן. ניתן גם לגרור לכאן תמונות.",
+ "bootbox.ok": "אישור",
"bootbox.cancel": "בטל",
"bootbox.confirm": "אשר",
- "cover.dragging_title": "מיקום תמונת הנושא",
- "cover.dragging_message": "גרור את תמונת הנושא למיקום הרצוי ולחץ על \"שמור\"",
- "cover.saved": "תמונת הנושא ומיקומה נשמרו"
+ "cover.dragging_title": "מיקום תמונת נושא",
+ "cover.dragging_message": "גרור תמונת נושא למיקום הרצוי ולחץ על \"שמור\"",
+ "cover.saved": "תמונת הנושא והמיקום נשמר"
}
\ No newline at end of file
diff --git a/public/language/he/notifications.json b/public/language/he/notifications.json
index 8d02ec113b..797ca6f387 100644
--- a/public/language/he/notifications.json
+++ b/public/language/he/notifications.json
@@ -24,7 +24,7 @@
"upvoted_your_post_in_dual": "%1 ו%2 הצביעו בעד הפוסט שלך ב%3",
"upvoted_your_post_in_multiple": "%1 ו%2 אחרים הצביעו לפוסט שלך ב%3.",
"moved_your_post": "%1 העביר את הפוסט שלך ל%2",
- "moved_your_topic": "%1 הוזז ל%2",
+ "moved_your_topic": "%1 הזיז את %2",
"user_flagged_post_in": "%1 דיווח על פוסט ב %2",
"user_flagged_post_in_dual": "%1 ו%2 סימנו פוסט ב%3",
"user_flagged_post_in_multiple": "%1 ו%2 נוספים סימנו פוסט ב%3",
diff --git a/public/language/he/topic.json b/public/language/he/topic.json
index 8909b61143..f7f3c94bff 100644
--- a/public/language/he/topic.json
+++ b/public/language/he/topic.json
@@ -15,36 +15,36 @@
"one_reply_to_this_post": "תגובה 1",
"last_reply_time": "תגובה אחרונה",
"reply-as-topic": "הגב כנושא",
- "guest-login-reply": "התחבר כדי לפרסם תגובה",
- "login-to-view": "🔒 התחבר כדי לצפות",
+ "guest-login-reply": "התחבר בכדי לפרסם תגובה",
+ "login-to-view": "🔒 התחבר בכדי לצפות",
"edit": "עריכה",
"delete": "מחק",
"purge": "מחק לצמיתות",
"restore": "שחזר",
- "move": "הזז",
- "change-owner": "שנה מחבר הודעה",
- "fork": "פורק",
+ "move": "העבר",
+ "change-owner": "שנה שם כותב הפוסט",
+ "fork": "פצל",
"link": "לינק",
"share": "שתף",
"tools": "כלים",
"locked": "נעול",
"pinned": "נעוץ",
"moved": "הועבר",
- "moved-from": "Moved from %1",
- "copy-ip": "העתק כתובת IP",
- "ban-ip": "הרחק כתובת IP",
+ "moved-from": "הועבר מ-%1",
+ "copy-ip": "העתק IP",
+ "ban-ip": "הרחק IP",
"view-history": "ערוך היסטוריה",
- "bookmark_instructions": "לחץ כאן לחזור לפוסט האחרון שקראת בנושא הזה.",
- "flag-post": "Flag this post",
- "flag-user": "Flag this user",
- "already-flagged": "Already Flagged",
- "view-flag-report": "View Flag Report",
- "merged_message": "This topic has been merged into %2",
- "deleted_message": "נושא זה נמחק. רק משתמשים עם ההרשאות המתאימות יכולים לצפות בו.",
- "following_topic.message": "מעתה, תקבל הודעות כאשר מישהו יעלה פוסט לנושא זה.",
- "not_following_topic.message": "תוכל לראות נושא זה ברשימת הנושאים שלא נקראו, אולם לא תוכל לקבל התראות כשמישהו יעלה פוסט על נושא זה.",
- "ignoring_topic.message": "לא תוכל לראות עוד נושא זה ברשימת הנושאים של נקראו. תקבל הודעה כשאתה תוזכר או כשהפוסט שלך יקבל הצבעה חיובית",
- "login_to_subscribe": "אנא הרשם או התחבר על-מנת לעקוב אחר נושא זה.",
+ "bookmark_instructions": "לחץ כאן בכדי לחזור לפוסט האחרון שקראת בנושא הזה.",
+ "flag-post": "דווח על פוסט זה",
+ "flag-user": "דווח על משתמש זה",
+ "already-flagged": "דווח כבר",
+ "view-flag-report": "הצג דוח דיווחים",
+ "merged_message": "נושא זה מוזג בתוך %2",
+ "deleted_message": "נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.",
+ "following_topic.message": "מעתה, תקבל התראה כאשר מישהו יעלה פוסט לנושא זה.",
+ "not_following_topic.message": "תוכל לראות נושא זה ברשימת הנושאים שלא נקראו, אולם לא תקבל התראה כשמישהו יעלה פוסט בנושא זה.",
+ "ignoring_topic.message": "לא תראה עוד נושא זה ברשימת הנושאים של נקראו. תקבל הודעה כשתוזכר או כשהפוסט שלך יקבל הצבעה חיובית",
+ "login_to_subscribe": "הרשם או התחבר בכדי לעקוב אחר נושא זה.",
"markAsUnreadForAll.success": "נושא זה סומן כלא נקרא לכולם.",
"mark_unread": "סמן כלא נקרא",
"mark_unread.success": "הנושא סומן כלא נקרא.",
@@ -56,9 +56,9 @@
"watching": "עוקב",
"not-watching": "לא עוקב",
"ignoring": "מתעלם",
- "watching.description": "הודע לי על תגובות חדשות.
הצג נושא חדש ברשימת הלא נקראו.",
- "not-watching.description": "אל תיידע אותי על תגובות חדשות.
הצג נושא חדש ברשימת הלא נקראו במידה ובחרתי לא להתעלם מקבוצת הדיון",
- "ignoring.description": "אל תתריע לי על תגובות חדשות.
אל תראה את הנושא בנושאים שלא נקראו ",
+ "watching.description": "הודע לי על תגובות חדשות.
הצג נושא ברשימת הלא נקראו.",
+ "not-watching.description": "אל תודיע לי על תגובות חדשות.
הצג נושא ברשימת הלא נקראו במידה ובחרתי לא להתעלם מקטגוריה זו",
+ "ignoring.description": "אל תודיע לי על תגובות חדשות.
אל תראה את הנושא ברשימת הלא נקראו ",
"thread_tools.title": "כלי נושא",
"thread_tools.markAsUnreadForAll": "סמן לא נקרא לכולם",
"thread_tools.pin": "נעץ נושא",
@@ -68,89 +68,91 @@
"thread_tools.move": "הזז נושא",
"thread_tools.move-posts": "הזז פוסטים",
"thread_tools.move_all": "הזז הכל",
- "thread_tools.change_owner": "שנה את כותב ההודעה",
+ "thread_tools.change_owner": "שנה שם כותב הפוסט",
"thread_tools.select_category": "בחר קטגוריה",
- "thread_tools.fork": "שכפל נושא",
+ "thread_tools.fork": "פצל נושא",
"thread_tools.delete": "מחק נושא",
"thread_tools.delete-posts": "מחק פוסטים",
- "thread_tools.delete_confirm": "אתה בטוח שאתה רוצה למחוק את הנושא הזה?",
+ "thread_tools.delete_confirm": "האם אתה בטוח שאתה רוצה למחוק נושא זה?",
"thread_tools.restore": "שחזר נושא",
- "thread_tools.restore_confirm": "אתה בטוח שאתה רוצה לשחזר את הנושא הזה?",
+ "thread_tools.restore_confirm": "האם אתה בטוח שאתה רוצה לשחזר נושא זה?",
"thread_tools.purge": "מחק נושא",
- "thread_tools.purge_confirm": "אתה בטוח שאתה רוצה למחוק את הנושא הזה?",
+ "thread_tools.purge_confirm": "האם אתה בטוח שאתה רוצה למחוק נושא זה?",
"thread_tools.merge_topics": "מזג נושאים",
"thread_tools.merge": "מזג",
- "topic_move_success": "נושא זה יועבר ל\"%1\" בקרוב. לחץ כאן לבטל.",
- "topic_move_multiple_success": "These topics will be moved to \"%1\" shortly. Click here to undo.",
- "topic_move_all_success": "All topics will be moved to \"%1\" shortly. Click here to undo.",
- "topic_move_undone": "Topic move undone",
- "topic_move_posts_success": "Posts will be moved shortly. Click here to undo.",
- "topic_move_posts_undone": "Post move undone",
- "post_delete_confirm": "אתה בטוח שאתה רוצה למחוק את הפוסט הזה?",
- "post_restore_confirm": "אתה בטוח שאתה רוצה לשחזר את הפוסט הזה?",
- "post_purge_confirm": "אתה בטוח שאתה רוצה למחוק את הפוסט הזה?",
+ "topic_move_success": "נושא זה יועבר תיקף ל\"%1\". לחץ כאן כדי לבטל.",
+ "topic_move_multiple_success": "נושאים אלו יועברו תיקף ל\"%1\" . לחץ כאן לביטול.",
+ "topic_move_all_success": "כל הנושאים יועברו תיקף ל\"%1\". לחץ כאן לביטול.",
+ "topic_move_undone": "העברת הנושא בוטל",
+ "topic_move_posts_success": "הפוסטים יועברו תיקף ל\"%1\" . לחץ כאן לביטול.",
+ "topic_move_posts_undone": "העברת הפוסט בוטל",
+ "post_delete_confirm": "האם אתה בטוח שאתה רוצה למחוק פוסט זה?",
+ "post_restore_confirm": "האם אתה בטוח שאתה רוצה לשחזר פוסט זה?",
+ "post_purge_confirm": "האם אתה בטוח שאתה רוצה למחוק פוסט זה?",
+ "pin-modal-expiry": "תאריך תפוגה",
+ "pin-modal-help": "באפשרותך להגדיר כאן תאריך תפוגה לנושא(ים) המוצמד(ים). לחלופין, אתה יכול להשאיר שדה זה ריק כדי שהנושא יישאר נעוץ עד לביטול ההצמדה ידנית.",
"load_categories": "טוען קטגוריות",
"confirm_move": "הזז",
- "confirm_fork": "שכפל",
+ "confirm_fork": "פצל",
"bookmark": "הוסף למועדפים",
"bookmarks": "מועדפים",
- "bookmarks.has_no_bookmarks": "לא צירפת אף פוסט למועדפים עדיין",
+ "bookmarks.has_no_bookmarks": "לא צירפת פוסט למועדפים עדיין",
"loading_more_posts": "טוען פוסטים נוספים",
"move_topic": "הזז נושא",
"move_topics": "הזז נושאים",
"move_post": "הזז פוסט",
"post_moved": "הפוסט הועבר!",
- "fork_topic": "שכפל נושא",
- "fork_topic_instruction": "לחץ על הפוסטים שברצונך לשכפל",
- "fork_no_pids": "לא בחרת אף פוסט!",
- "no-posts-selected": "No posts selected!",
- "x-posts-selected": "%1 post(s) selected",
- "x-posts-will-be-moved-to-y": "%1 post(s) will be moved to \"%2\"",
+ "fork_topic": "פצל נושא",
+ "fork_topic_instruction": "לחץ על הפוסטים שברצונך לפצל",
+ "fork_no_pids": "לא נבחרו פוסטים!",
+ "no-posts-selected": "לא נבחרו פוסטים!",
+ "x-posts-selected": "%1 פוסט(ים) נבחרו",
+ "x-posts-will-be-moved-to-y": "%1 פוסט(ים) יועברו ל-\"%2\"",
"fork_pid_count": "%1 פוסט(ים) נבחרו",
- "fork_success": "הפוסט שוכפל בהצלחה! לחץ כאן על מנת לעבור לפוסט המשוכפל.",
+ "fork_success": "הפוסט פוצל בהצלחה! לחץ כאן על מנת לעבור לפוסט המפוצל.",
"delete_posts_instruction": "לחץ על הפוסטים שברצונך למחוק",
- "merge_topics_instruction": "Click the topics you want to merge or search for them",
- "merge-topic-list-title": "List of topics to be merged",
- "merge-options": "Merge options",
- "merge-select-main-topic": "Select the main topic",
- "merge-new-title-for-topic": "New title for topic",
- "move_posts_instruction": "Click the posts you want to move then go to target topic and click move.",
- "change_owner_instruction": "לחץ על ההודעה שהנך רוצה לשנות את בעל ההודעה",
+ "merge_topics_instruction": "לחץ על הנושאים שברצונך למזג או חפש אותם",
+ "merge-topic-list-title": "רשימת הנושאים למיזוג",
+ "merge-options": "אפשרויות מיזוג",
+ "merge-select-main-topic": "בחר את הכותרת המועדפת",
+ "merge-new-title-for-topic": "כותרת חדשה לנושא",
+ "move_posts_instruction": "לחץ על הפוסטים שברצונך להעביר ואז עבור לנושא היעד ולחץ על העבר.",
+ "change_owner_instruction": "לחץ על הפוסטים עליהם תרצה לשנות את כותב ההודעה",
"composer.title_placeholder": "הכנס את כותרת הנושא כאן...",
- "composer.handle_placeholder": "הזן את שמך / כינויי שלך כאן",
+ "composer.handle_placeholder": "הזן את שמך / כינוי שלך כאן",
"composer.discard": "ביטול",
"composer.submit": "שלח",
- "composer.replying_to": "מגיב ל %1",
+ "composer.replying_to": "מגיב ל%1",
"composer.new_topic": "נושא חדש",
"composer.uploading": "מעלה...",
- "composer.thumb_url_label": "הדבק את כתובת ה URL לתמונה המוקטנת עבור הנושא",
+ "composer.thumb_url_label": "הדבק את כתובת ה-URL לתמונה מוקטנת עבור הנושא",
"composer.thumb_title": "הוסף תמונה מוקטנת לנושא זה",
"composer.thumb_url_placeholder": "http://example.com/thumb.png",
"composer.thumb_file_label": "או העלה קובץ",
"composer.thumb_remove": "נקה שדות",
"composer.drag_and_drop_images": "גרור תמונות לכאן",
- "more_users_and_guests": "%1 משתמשים ו%2 אורחים נוספים",
+ "more_users_and_guests": "%1 משתמשים נוספים ו-%2 אורחים",
"more_users": "%1 משתמשים נוספים",
"more_guests": "%1 אורחים נוספים",
- "users_and_others": "%1 ו%2 אחרים",
+ "users_and_others": "%1 ו-%2 אחרים",
"sort_by": "סדר על-פי",
"oldest_to_newest": "מהישן לחדש",
"newest_to_oldest": "מהחדש לישן",
"most_votes": "הכי הרבה הצבעות",
"most_posts": "הכי הרבה פוסטים",
- "stale.title": "פתח נושא חדש במקום?",
- "stale.warning": "הנושא בו אתה מגיב הוא דיי ישן. האם ברצונך לפתוח נושא חדש, ולהזכיר את הנושא הזה בתגובתך?",
+ "stale.title": "ליצור נושא חדש במקום זאת?",
+ "stale.warning": "הנושא בו אתה מגיב הוא די ישן. האם ברצונך לפתוח נושא חדש, ולהזכיר נושא זה בתגובתך?",
"stale.create": "צור נושא חדש",
"stale.reply_anyway": "הגב לנושא זה בכל מקרה",
"link_back": "תגובה: [%1](%2)",
"diffs.title": "היסטוריית עריכת הפוסט",
"diffs.description": "להודעה זו יש %1 עריכות. לחץ על אחת מהעריכות להלן כדי לראות את תוכן ההודעה בנקודת זמן זו.",
"diffs.no-revisions-description": "לפוסט זה יש %1גרסאות",
- "diffs.current-revision": "גירסה נוכחית",
- "diffs.original-revision": "גירסה מקורית",
- "diffs.restore": "Restore this revision",
- "diffs.restore-description": "A new revision will be appended to this post's edit history.",
- "diffs.post-restored": "Post successfully restored to earlier revision",
- "timeago_later": "אחרי %1:",
+ "diffs.current-revision": "גרסה נוכחית",
+ "diffs.original-revision": "גרסה מקורית",
+ "diffs.restore": "שחזר גרסה זו",
+ "diffs.restore-description": "גרסה חדשה תצורף להיסטוריית העריכות של פוסט זה.",
+ "diffs.post-restored": "הפוסט שוחזר בהצלחה לגרסה קודמת",
+ "timeago_later": "אחרי %1",
"timeago_earlier": "לפני %1 "
}
\ No newline at end of file
diff --git a/public/language/he/user.json b/public/language/he/user.json
index a9e026b9a8..7f070c79f5 100644
--- a/public/language/he/user.json
+++ b/public/language/he/user.json
@@ -8,30 +8,30 @@
"email": "כתובת אימייל",
"confirm_email": "אשר מייל",
"account_info": "פרטי חשבון",
- "admin_actions_label": "פעולות ניהוליות",
+ "admin_actions_label": "פעולות ניהול",
"ban_account": "הרחק חשבון",
- "ban_account_confirm": "אתה בטוח שברצונך להרחיק את המשתמש הזה?",
+ "ban_account_confirm": "האם אתה בטוח שאתה רוצה להרחיק משתמש זה?",
"unban_account": "בטל את הרחקת החשבון",
"delete_account": "מחק חשבון",
"delete_account_as_admin": "מחק חשבון",
"delete_content": "מחק תוכן חשבון",
"delete_all": "מחק חשבון ותוכן",
- "delete_account_confirm": "Are you sure you want to anonymize your posts and delete your account?
This action is irreversible and you will not be able to recover any of your data
Enter your password to confirm that you wish to destroy this account.",
- "delete_this_account_confirm": "Are you sure you want to delete this account while leaving its contents behind?
This action is irreversible, posts will be anonymized, and you will not be able to restore post associations with the deleted account
",
- "delete_account_content_confirm": "Are you sure you want to delete this account's content (posts/topics/uploads)?
This action is irreversible and you will not be able to recover any data
",
- "delete_all_confirm": "Are you sure you want to delete this account and all of its content (posts/topics/uploads)?
This action is irreversible and you will not be able to recover any data
",
+ "delete_account_confirm": "האם אתה בטוח שברצונך להפוך את הפוסטים שלך לאנונימיים ולמחוק את החשבון שלך?
פעולה זו היא בלתי הפיכה ולא תוכל לשחזר את הנתונים שלך
הזן את הסיסמה שלך על מנת לאשר שברצונך להשמיד חשבון זה.",
+ "delete_this_account_confirm": "האם אתה בטוח שברצונך למחוק חשבון זה תוך השארת התוכן שלו?
פעולה זו היא בלתי הפיכה, הפוסטים יהפכו לאנונימיים, ולא תוכל לשחזר שיוכי הפוסטים עם החשבון שנמחק
",
+ "delete_account_content_confirm": "האם אתה בטוח שברצונך למחוק את התוכן של חשבון זה (פוסטים/נושאים/העלאות)?
פעולה זו היא בלתי הפיכה ולא תוכל לשחזר שום נתונים
",
+ "delete_all_confirm": "האם אתה בטוח שברצונך למחוק חשבון זה ואת כל התוכן שלו (פוסטים/נושאים/העלאות)?
פעולה זו היא בלתי הפיכה ולא תוכל לשחזר שום נתונים
",
"account-deleted": "החשבון נמחק",
"account-content-deleted": "תוכן החשבון נמחק",
"fullname": "שם מלא",
"website": "אתר",
"location": "מיקום",
"age": "גיל",
- "joined": "הצטרף",
+ "joined": "הצטרף ב-",
"lastonline": "התחבר לאחרונה",
"profile": "פרופיל",
"profile_views": "צפיות בפרופיל",
"reputation": "מוניטין",
- "bookmarks": "מועדפים",
+ "bookmarks": "סימניות",
"watched_categories": "קטגוריות במעקב",
"change_all": "שנה הכל",
"watched": "נצפה",
@@ -57,13 +57,13 @@
"change_picture": "שנה תמונה",
"change_username": "שנה שם משתמש",
"change_email": "שנה מייל",
- "email_same_as_password": "אנא כתוב את הסיסמא הנוכחית שלך כדי להמשיך – כתבת את כתובת המייל החדשה במקום.",
+ "email_same_as_password": "הכנס את הסיסמא הנוכחית שלך על מנת להמשיך – כתבת את כתובת המייל החדשה במקום.",
"edit": "ערוך",
"edit-profile": "ערוך פרופיל",
"default_picture": "אייקון ברירת מחדל",
"uploaded_picture": "התמונה הועלתה",
"upload_new_picture": "העלה תמונה חדשה",
- "upload_new_picture_from_url": "העלה תמונה חדשה מ URL",
+ "upload_new_picture_from_url": "העלה תמונה חדשה מ-URL",
"current_password": "סיסמה נוכחית",
"change_password": "שנה סיסמה",
"change_password_error": "סיסמה אינה תקינה!",
@@ -73,23 +73,23 @@
"change_password_success": "הסיסמה שלך עודכנה!",
"confirm_password": "אמת סיסמה",
"password": "סיסמה",
- "username_taken_workaround": "שם המשתמש שבחרת כבר תפוס, אז שינינו אותו מעט. שם המשתמש שלך כעת הוא %1",
- "password_same_as_username": "הסיסמה שלך זהה לשם המשתמש, אנא בחר סיסמה שונה.",
- "password_same_as_email": "הסיסמה שלך זהה לכתובת המייל שלך, אנא בחר סיסמה שונה.",
+ "username_taken_workaround": "שם המשתמש שבחרת כבר תפוס, ולכן שינינו אותו מעט. שם המשתמש שלך כעת הוא %1",
+ "password_same_as_username": "הסיסמה שלך זהה לשם המשתמש, בחר סיסמה שונה.",
+ "password_same_as_email": "הסיסמה שלך זהה לכתובת המייל שלך, בחר סיסמה שונה.",
"weak_password": "סיסמה חלשה.",
"upload_picture": "העלה תמונה",
"upload_a_picture": "העלה תמונה",
"remove_uploaded_picture": "מחק את התמונה שהועלתה",
"upload_cover_picture": "העלה תמונת נושא",
- "remove_cover_picture_confirm": "האם אתה בטוח שאתה רוצה למחוק את תמונת הרקע?",
+ "remove_cover_picture_confirm": "האם אתה בטוח שאתה רוצה למחוק תמונת נושא?",
"crop_picture": "חתוך תמונה",
"upload_cropped_picture": "חתוך והעלה",
"settings": "הגדרות",
- "show_email": "פרסם את כתובת האימייל שלי",
+ "show_email": "הצג את כתובת האימייל שלי",
"show_fullname": "הצג את שמי המלא",
"restrict_chats": "אשר הודעות צ'אט ממשתמשים שאני עוקב אחריהם בלבד",
"digest_label": "הרשם לקבלת תקציר",
- "digest_description": "הרשם לקבלת עדכונים מפורום זה (התראות ונושאים חדשים) על-פי לוח זמנים קבוע מראש.",
+ "digest_description": "הירשם לקבלת עדכונים בדואר אלקטרוני מפורום זה (הודעות ונושאים חדשים) בהתאם ללוח זמנים מוגדר מראש",
"digest_off": "כבוי",
"digest_daily": "יומי",
"digest_weekly": "שבועי",
@@ -106,46 +106,46 @@
"has_no_blocks": "לא חסמת אף משתמש.",
"email_hidden": "כתובת אימייל מוסתרת",
"hidden": "מוסתר",
- "paginate_description": "הצג נושאים ופוסטים כדפים במקום כרשימת גלילה אין-סופית",
+ "paginate_description": "הצג נושאים ופוסטים בעמודים במקום כרשימת גלילה אין-סופית",
"topics_per_page": "כמות נושאים בעמוד",
"posts_per_page": "כמות פוסטים בעמוד",
"max_items_per_page": "מקסימום %1",
- "acp_language": "שפת עמוד המנהל",
+ "acp_language": "שפת עמוד הניהול",
"notifications": "התראות",
"upvote-notif-freq": "תדירות התראת הצבעה חיובית",
"upvote-notif-freq.all": "כל ההצבעות החיוביות",
- "upvote-notif-freq.first": "הראשון לפוסט",
+ "upvote-notif-freq.first": "הראשון בפוסט",
"upvote-notif-freq.everyTen": "כל 10 הצבעות חיוביות",
"upvote-notif-freq.threshold": "ב-1, 5, 10, 25, 50, 100, 150, 200...",
- "upvote-notif-freq.logarithmic": "ב10, 100, 1000...",
+ "upvote-notif-freq.logarithmic": "ב-10, 100, 1000...",
"upvote-notif-freq.disabled": "מבוטל",
- "browsing": "הגדרות צפייה",
+ "browsing": "הגדרות ניווט",
"open_links_in_new_tab": "פתח קישורים חיצוניים בכרטיסייה חדשה",
"enable_topic_searching": "הפעל חיפוש בתוך נושא",
- "topic_search_help": "אם מאופשר, החיפוש בתוך הנושא יעקוף את שיטת החיפוש של הדפדפן, ויאפשר לך לחפש בכל הנושא - ולא רק במה שמוצג על המסך",
- "update_url_with_post_index": "Update url with post index while browsing topics",
+ "topic_search_help": "אם מופעל, החיפוש בתוך הנושא יעקוף את שיטת החיפוש של הדפדפן, ויאפשר לך לחפש בכל הנושא - ולא רק במה שמוצג על המסך, עם זאת בלחיצה שניה על Ctrl+5 ייפתח לך החיפוש הרגיל של הדפדפן",
+ "update_url_with_post_index": "עדכון כתובת ה-URL עם אינדקס הפוסט בעת גלישה בנושאים",
"scroll_to_my_post": "הצג את הפוסט לאחר פרסום התגובה",
"follow_topics_you_reply_to": "עקוב אחר נושאים שהגבת עליהם",
- "follow_topics_you_create": "עקוב אחר נושאים שפרסמת",
+ "follow_topics_you_create": "עקוב אחר נושאים שייצרת",
"grouptitle": "כותרת הקבוצה",
"group-order-help": "בחר קבוצה והשתמש בחצים על מנת לארגן כותרות",
"no-group-title": "ללא כותרת לקבוצה",
"select-skin": "בחר מראה",
"select-homepage": "בחר דף בית",
"homepage": "דף הבית",
- "homepage_description": "בחר דף שיהיה דף הבית של הפורום או \"כלום\" על מנת להשתמש בדף הבית ברירת המחדל.",
+ "homepage_description": "בחר דף שיוגדר כדף הבית של הפורום או בחר ב\"כלום\" על מנת להשתמש בדף הבית הברירת מחדל.",
"custom_route": "נתיב דף הבית המותאם-אישית",
- "custom_route_help": "הכנס שם נתיב כאן, ללא לוכסן לפני (לדוגמא \"אחרונים\", או \"פופולארי\")",
- "sso.title": "שירות יחיד להתחברות",
+ "custom_route_help": "הכנס שם נתיב כאן, ללא לוכסן לפני (לדוגמא \"recent\" לאחרונים, או \"popular\" לפופולארי)",
+ "sso.title": "Single Sign-on Services",
"sso.associated": "משוייך עם",
"sso.not-associated": "לחץ כאן כדי לשייך",
"sso.dissociate": "ביטול שיוך",
"sso.dissociate-confirm-title": "אשר ביטול שיוך",
- "sso.dissociate-confirm": "האם אתה בטוח שאתה רוצה לבטל את שיוך החשבון שלך מ%1?",
+ "sso.dissociate-confirm": "האם אתה בטוח שאתה רוצה לבטל שיוך חשבונך מ%1?",
"info.latest-flags": "דיווחים אחרונים",
- "info.no-flags": "לא נמצאו פוסטים שמשתמשים דיווחו עליהם",
+ "info.no-flags": "לא נמצאו פוסטים מדווחים",
"info.ban-history": "היסטוריית הרחקות",
- "info.no-ban-history": "משתמש זה מעולם לא הורחק",
+ "info.no-ban-history": "משתמש זה לא הורחק מעולם",
"info.banned-until": "הורחק עד %1",
"info.banned-expiry": "פג תוקף",
"info.banned-permanently": "הורחק לצמיתות",
@@ -156,15 +156,15 @@
"info.moderation-note": "הערת מודרטור",
"info.moderation-note.success": "הערת מודרטור נשמרה",
"info.moderation-note.add": "הוסף הערה",
- "sessions.description": "דף זה מאפשר לך לראות את כל הסשנים הפעילים בפורום זה ולבטל אותם במידת הצורך. אתה יכול לבטל את הסשן שלך על ידי התנתקות מהמשתמש.",
+ "sessions.description": "דף זה מאפשר לך לראות את כל הסשנים הפעילים בפורום זה ולבטל אותם במידת הצורך. אתה יכול לבטל את הסשן שלך על ידי התנתקותך מהחשבון.",
"consent.title": "תנאי השימוש באתר",
"consent.lead": "אתר זה אוסף ומעבד נתונים הכוללים בחלקם את המידע האישי שלך.",
"consent.intro": "אנו משתמשים במידע שנאסף כדי להתאים אישית את החוויה שלך, וכן לקשר את ההודעות שאתה מבצע לחשבון המשתמש שלך. במהלך שלב ההרשמה התבקשת לספק שם משתמש וכתובת דוא\"ל, תוכל גם לספק מידע נוסף כדי להשלים את פרופיל המשתמש שלך באתר זה.
אנו שומרים ומעבדים מידע זה. אתה יכול לבטל את הסכמתך בכל עת על ידי מחיקת החשבון שלך. בכל עת תוכל לבקש עותק של חשבונך לאתר זה, באמצעות דף זה.
אם יש לך שאלות או חששות, אנו ממליצים לך ליצור קשר עם צוות הניהול של האתר.",
- "consent.email_intro": "מדי פעם, אנו עשויים לשלוח הודעות לכתובת הדוא\"ל הרשומה שלך על מנת לספק עדכונים ו / או להודיע לך על פעילות חדשה הרלוונטית עבורך. ניתן להתאים אישית את התדירות של העדכונים (כולל השבתתם), וכן לבחור אילו סוגי הודעות לקבל באמצעות דוא\"ל, דרך דף הגדרות המשתמש שלך.",
- "consent.digest_frequency": "אלא אם כן תשנה במפורש את הגדרות המשתמש שלך, אתר זה מספק עדכוני דוא\"ל בכל %1.",
- "consent.digest_off": "אלא אם כן תשנה במפורש את הגדרות המשתמש שלך, האתר לא ישלח הודעות תמצית.",
+ "consent.email_intro": "אנו עשויים מדי פעם לשלוח הודעות לכתובת הדוא\"ל שלך על מנת לספק לך עדכונים ו/או להודיע לך על פעילות חדשה הרלוונטית עבורך. ניתן להתאים אישית את התדירות של העדכונים (כולל השבתתם), וכן לבחור אילו סוגי הודעות לקבל באמצעות הדוא\"ל דרך דף הגדרות המשתמש שלך.",
+ "consent.digest_frequency": " אתר זה מספק עדכוני דוא\"ל בכל %1. אם תשבית את האפשרות הזאת בהגדרות המשתמש שלך לא תקבל עדכונים אלו.",
+ "consent.digest_off": "האתר לא ישלח הודעות תקציר, אלא אם כן תשנה זאת במפורש בהגדרות המשתמש שלך.",
"consent.received": "הסכמתך לאפשר לאתר לאסוף ולעבד את המידע שלך התקבלה. אין צורך בפעולה נוספת.",
- "consent.not_received": "הסכמתך לאיסוף נתונים ועיבודם לא התקבלה. מנהל האתר רשאי למחוק את חשבונך בכל עת על מנת להיות תואם לתקנות רגולציה כגון GDPR,.",
+ "consent.not_received": "לא סיפקת אישור לאיסוף ועיבוד נתונים. בכל עת הנהלת האתר רשאית למחוק את חשבונך בכדי להתאים את עצמה בתקנה הכללית להגנת נתונים.",
"consent.give": "הסכם",
"consent.right_of_access": "זכותך לנגישות",
"consent.right_of_access_description": "שמורה לך הזכות לגשת לנתונים שנאספו על ידי האתר. תוכל לאחזר עותק של נתונים אלה על ידי לחיצה על הלחצן מטה.",
diff --git a/public/language/hr/admin/settings/api.json b/public/language/hr/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/hr/admin/settings/api.json
+++ b/public/language/hr/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/hr/admin/settings/user.json b/public/language/hr/admin/settings/user.json
index c224db4469..abaddd6cf4 100644
--- a/public/language/hr/admin/settings/user.json
+++ b/public/language/hr/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Dozvoli brisanje računa korisnicima",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Predlošci",
"disable-user-skins": "Onemogući korisnicima odabir predloška",
"account-protection": "Zaštita računa",
diff --git a/public/language/hr/error.json b/public/language/hr/error.json
index b6e21873f3..be8b52316f 100644
--- a/public/language/hr/error.json
+++ b/public/language/hr/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Netočan ID teme",
"invalid-pid": "Netočan ID objave",
"invalid-uid": "Netočan ID korisnika",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Netočno korisničko ime",
"invalid-email": "Netočan email",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/hr/topic.json b/public/language/hr/topic.json
index ba109d49a7..fd3c6ce0a3 100644
--- a/public/language/hr/topic.json
+++ b/public/language/hr/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Sigurni ste da želite obrisati ovu objavu?",
"post_restore_confirm": "Sigurni ste da želite povratiti ovu objavu?",
"post_purge_confirm": "Sigurni ste da želite odbaciti ovu objavu?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Učitavam kategorije",
"confirm_move": "Pomakni",
"confirm_fork": "Dupliraj",
diff --git a/public/language/hu/admin/settings/api.json b/public/language/hu/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/hu/admin/settings/api.json
+++ b/public/language/hu/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/hu/admin/settings/user.json b/public/language/hu/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/hu/admin/settings/user.json
+++ b/public/language/hu/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/hu/error.json b/public/language/hu/error.json
index 438ff0a371..ec4ab33aff 100644
--- a/public/language/hu/error.json
+++ b/public/language/hu/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Érvénytelen témakör azonosító",
"invalid-pid": "Érvénytelen hozzászólás azonosító",
"invalid-uid": "Érvénytelen felhasználó azonosító",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Érvénytelen felhasználónév",
"invalid-email": "Érvénytelen e-mail cím",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/hu/topic.json b/public/language/hu/topic.json
index 3825796197..b5ddcc01db 100644
--- a/public/language/hu/topic.json
+++ b/public/language/hu/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Biztos törölni akarod a hozzászólást?",
"post_restore_confirm": "Biztos vissza akarod állítani a hozzászólást?",
"post_purge_confirm": "Biztos végleg törölni akarod a hozzászólást?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Kategóriák betöltése",
"confirm_move": "Áthelyezés",
"confirm_fork": "Szétszedés",
diff --git a/public/language/id/admin/settings/api.json b/public/language/id/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/id/admin/settings/api.json
+++ b/public/language/id/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/id/admin/settings/user.json b/public/language/id/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/id/admin/settings/user.json
+++ b/public/language/id/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/id/error.json b/public/language/id/error.json
index f3ccd5f662..615ee12b07 100644
--- a/public/language/id/error.json
+++ b/public/language/id/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID Topik Salah",
"invalid-pid": "ID Post Salah",
"invalid-uid": "ID User Salah",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Username Salah",
"invalid-email": "Email Salah",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/id/topic.json b/public/language/id/topic.json
index 97fa869ae0..93a3d03f86 100644
--- a/public/language/id/topic.json
+++ b/public/language/id/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Kamu yakin ingin menghapus posting ini?",
"post_restore_confirm": "Kamu yakin ingin mengembalikan posting ini?",
"post_purge_confirm": "Kamu yakin ingin memusnahkan posting ini?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Memuat Kategori",
"confirm_move": "Pindah",
"confirm_fork": "Cabangkan",
diff --git a/public/language/it/admin/manage/privileges.json b/public/language/it/admin/manage/privileges.json
index a9d631e1d8..1d3327aab7 100644
--- a/public/language/it/admin/manage/privileges.json
+++ b/public/language/it/admin/manage/privileges.json
@@ -9,7 +9,7 @@
"upload-files": "Carica file",
"signature": "Firma",
"ban": "Ban",
- "invite": "Invite",
+ "invite": "Invita",
"search-content": "Cerca contenuto",
"search-users": "Cerca utenti",
"search-tags": "Cerca tag",
diff --git a/public/language/it/admin/settings/api.json b/public/language/it/admin/settings/api.json
index a815259b93..cb4a2e1cea 100644
--- a/public/language/it/admin/settings/api.json
+++ b/public/language/it/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Token",
+ "settings": "Settings",
"lead-text": "Da questa pagina è possibile configurare l'accesso alle API di scrittura in NodeBB.",
"intro": "Per impostazione predefinita, l'API di scrittura autentica gli utenti in base al cookie di sessione, ma NodeBB supporta anche l'autenticazione Bearer tramite token generati tramite questa pagina.",
"docs": "Clicca qui per accedere alle specifiche complete dell'API",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "ID utente",
"uid-help-text": "Specificare un ID utente da associare a questo token. Se l'ID utente è 0, sarà considerato un token master, che può assumere l'identità di altri utenti in base al parametro _uid",
"description": "Descrizione",
diff --git a/public/language/it/admin/settings/email.json b/public/language/it/admin/settings/email.json
index 5c7d376804..0f996e5154 100644
--- a/public/language/it/admin/settings/email.json
+++ b/public/language/it/admin/settings/email.json
@@ -36,6 +36,6 @@
"subscriptions.disable": "Disabilita email riepilogo",
"subscriptions.hour": "Orario riepilogo",
"subscriptions.hour-help": "Si prega di inserire un numero che rappresenta l'ora per l'invio dell'email programmate (es. 0per mezzanotte, 17per le 17: 00). Tieni presente che questa è l'ora secondo il server stesso, e potrebbe non combaciare esattamente al tuo orologio di sistema.
L'orario approssimativo del server è:
La prossima trasmissione giornaliera è prevista alle ",
- "notifications.settings": "Email notification settings",
- "notifications.remove-images": "Remove images from email notifications"
+ "notifications.settings": "Impostazioni di notifica email",
+ "notifications.remove-images": "Rimuovi le immagini dalle notifiche email"
}
\ No newline at end of file
diff --git a/public/language/it/admin/settings/guest.json b/public/language/it/admin/settings/guest.json
index 798eb01eda..273aa7bf3f 100644
--- a/public/language/it/admin/settings/guest.json
+++ b/public/language/it/admin/settings/guest.json
@@ -1,7 +1,7 @@
{
- "settings": "Settings",
+ "settings": "Impostazioni",
"handles.enabled": "Consenti nome utente ospite",
"handles.enabled-help": "Questa opzione mostra un nuovo campo che permette agli ospiti di scegliere un nome da associare ad ogni post che fanno. Se disabilitata, saranno semplicemente chiamati \"Ospite\".",
"topic-views.enabled": "Consentire agli ospiti di aumentare il numero di visualizzazioni della discussione",
- "reply-notifications.enabled": "Allow guests to generate reply notifications"
+ "reply-notifications.enabled": "Consenti agli ospiti di generare notifiche di risposta"
}
\ No newline at end of file
diff --git a/public/language/it/admin/settings/user.json b/public/language/it/admin/settings/user.json
index db508fbac9..06c96da735 100644
--- a/public/language/it/admin/settings/user.json
+++ b/public/language/it/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Abilita cancellazione dell'account",
"hide-fullname": "Nascondi nome completo agli utenti",
"hide-email": "Nascondi l'email dagli utenti",
+ "show-fullname-as-displayname": "Mostra il nome completo dell'utente come nome visualizzato, se disponibile",
"themes": "Temi",
"disable-user-skins": "Non permettere agli utenti di scegliere una skin personalizzata",
"account-protection": "Protezione Account",
@@ -65,7 +66,7 @@
"restrict-chat": "Permetti messaggi in chat solo da utenti che seguo",
"outgoing-new-tab": "Apri link esterni in una nuova scheda",
"topic-search": "Abilita ricerca nella Discussione",
- "update-url-with-post-index": "Update url with post index while browsing topics",
+ "update-url-with-post-index": "Aggiorna l'url con l'indice dei posti durante la navigazione delle discussioni",
"digest-freq": "Iscriviti al Riepilogo",
"digest-freq.off": "Spento",
"digest-freq.daily": "Quotidiano",
diff --git a/public/language/it/error.json b/public/language/it/error.json
index 724ae2806e..c5fcbb72fb 100644
--- a/public/language/it/error.json
+++ b/public/language/it/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID Topic non valido",
"invalid-pid": "ID Post non valido",
"invalid-uid": "ID Utente non valido",
+ "invalid-date": "Deve essere fornita una data valida",
"invalid-username": "Nome utente non valido",
"invalid-email": "Email non valida",
"invalid-fullname": "Nome completo non valido",
diff --git a/public/language/it/flags.json b/public/language/it/flags.json
index 5473628978..040fcbbac6 100644
--- a/public/language/it/flags.json
+++ b/public/language/it/flags.json
@@ -67,7 +67,7 @@
"sort-upvotes": "Più voti positivi",
"sort-replies": "Più risposte",
- "modal-title": "Report Content",
+ "modal-title": "Segnala il contenuto",
"modal-body": "Specifica il motivo per cui contrassegni %1 %2 per la revisione. In alternativa, utilizza uno dei pulsanti di segnalazione rapida, se applicabile.",
"modal-reason-spam": "Spam",
"modal-reason-offensive": "Offensivo",
diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json
index 812cae26d4..567d3bc315 100644
--- a/public/language/it/notifications.json
+++ b/public/language/it/notifications.json
@@ -60,7 +60,7 @@
"notificationType_post-edit": "Quando un post viene modificato in un topic che stai guardando",
"notificationType_follow": "Quando qualcuno inizia a seguirti",
"notificationType_new-chat": "Quando ricevi un messaggio in chat",
- "notificationType_new-group-chat": "When you receive a group chat message",
+ "notificationType_new-group-chat": "Quando ricevi un messaggio di chat di gruppo",
"notificationType_group-invite": "Quando ricevi un invito ad un gruppo",
"notificationType_group-request-membership": "Quando qualcuno richiede di iscriversi a un gruppo di tua proprietà",
"notificationType_new-register": "Quando qualcuno è in attesa della registrazione",
diff --git a/public/language/it/topic.json b/public/language/it/topic.json
index 252ac98c60..4de7332d7d 100644
--- a/public/language/it/topic.json
+++ b/public/language/it/topic.json
@@ -30,7 +30,7 @@
"locked": "Bloccato",
"pinned": "Appeso",
"moved": "Spostato",
- "moved-from": "Moved from %1",
+ "moved-from": "Spostato da %1",
"copy-ip": "Copia indirizzo IP",
"ban-ip": "Banna indirizzo IP",
"view-history": "Modifica storico",
@@ -89,6 +89,8 @@
"post_delete_confirm": "Sei sicuro di voler eliminare questo post?",
"post_restore_confirm": "Sei sicuro di voler ripristinare questo post?",
"post_purge_confirm": "Sei sicuro di voler eliminare definitivamente questo post?",
+ "pin-modal-expiry": "Data di scadenza",
+ "pin-modal-help": "Facoltativamente, è possibile impostare una data di scadenza per le discussioni appuntate qui. In alternativa, è possibile lasciare vuoto questo campo per mantenere la discussione bloccata fino a quando non viene sbloccata manualmente.",
"load_categories": "Caricamento Categorie",
"confirm_move": "Sposta",
"confirm_fork": "Dividi",
diff --git a/public/language/it/user.json b/public/language/it/user.json
index c9335882d2..986e149234 100644
--- a/public/language/it/user.json
+++ b/public/language/it/user.json
@@ -123,7 +123,7 @@
"open_links_in_new_tab": "Apri i link web in una nuova pagina",
"enable_topic_searching": "Abilita la ricerca negli argomenti",
"topic_search_help": "Se abilitata, la ricerca negli argomenti ignorerà il comportamento predefinito del browser per consentirti di cercare all'interno delle discussioni, anziché soltanto nel contenuto visibile a schermo",
- "update_url_with_post_index": "Update url with post index while browsing topics",
+ "update_url_with_post_index": "Aggiorna l'url con l'indice dei posti durante la navigazione delle discussioni",
"scroll_to_my_post": "Dopo aver postato una risposta, mostra il nuovo post",
"follow_topics_you_reply_to": "Segui le discussioni a cui rispondi",
"follow_topics_you_create": "Segui le discussioni che crei",
diff --git a/public/language/it/users.json b/public/language/it/users.json
index 88220eca07..d18fb9fdfc 100644
--- a/public/language/it/users.json
+++ b/public/language/it/users.json
@@ -11,7 +11,7 @@
"online-only": "Solo online",
"invite": "Invita",
"prompt-email": "Email:",
- "groups-to-join": "Groups to be joined when invite is accepted:",
+ "groups-to-join": "Gruppi a cui iscriversi quando si accetta l'invito:",
"invitation-email-sent": "Una mail di invito è stata inviata a %1",
"user_list": "Lista Utenti",
"recent_topics": "Discussioni Recenti",
diff --git a/public/language/ja/admin/settings/api.json b/public/language/ja/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/ja/admin/settings/api.json
+++ b/public/language/ja/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/ja/admin/settings/user.json b/public/language/ja/admin/settings/user.json
index 0f07c6d471..e79e65efe7 100644
--- a/public/language/ja/admin/settings/user.json
+++ b/public/language/ja/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "アカウントが解除されました",
"hide-fullname": "ユーザーから、フルネームが見えないようにする。",
"hide-email": "ユーザーから、Emailが見えないようにする。",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "テーマ",
"disable-user-skins": "ユーザーがカスタムスキンを選択できないようにする",
"account-protection": "アカウント保護",
diff --git a/public/language/ja/error.json b/public/language/ja/error.json
index 5462f992a5..29e9d24829 100644
--- a/public/language/ja/error.json
+++ b/public/language/ja/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "無効なスレッドID",
"invalid-pid": "無効な投稿ID",
"invalid-uid": "無効なユーザーID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "無効なユーザー名",
"invalid-email": "無効なメール",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json
index 7b37b0d752..9c1bb2accf 100644
--- a/public/language/ja/topic.json
+++ b/public/language/ja/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "本当にこの投稿を削除しますか?",
"post_restore_confirm": "本当にこの投稿を元に戻しますか?",
"post_purge_confirm": "本当にこの投稿を切り離しますか?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "板をローディング中...",
"confirm_move": "移動",
"confirm_fork": "フォーク",
diff --git a/public/language/ko/admin/settings/api.json b/public/language/ko/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/ko/admin/settings/api.json
+++ b/public/language/ko/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/ko/admin/settings/user.json b/public/language/ko/admin/settings/user.json
index 0c42c390b2..11f03b2f26 100644
--- a/public/language/ko/admin/settings/user.json
+++ b/public/language/ko/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "계정 삭제 허용",
"hide-fullname": "사용자에게서 이름 숨기기",
"hide-email": "사용자에게서 이메일 숨기기",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "테마",
"disable-user-skins": "일반 사용자가 스킨 지정 금지",
"account-protection": "계정 보호",
diff --git a/public/language/ko/error.json b/public/language/ko/error.json
index 7181ba87e7..b6c411577e 100644
--- a/public/language/ko/error.json
+++ b/public/language/ko/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "올바르지 않은 게시물 ID입니다.",
"invalid-pid": "올바르지 않은 포스트 ID입니다.",
"invalid-uid": "올바르지 않은 사용자 ID입니다.",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "올바르지 않은 사용자명 입니다.",
"invalid-email": "올바르지 않은 이메일입니다.",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json
index 81aafa2234..f28052fc2a 100644
--- a/public/language/ko/topic.json
+++ b/public/language/ko/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "이 포스트를 삭제 하시겠습니까?",
"post_restore_confirm": "이 포스트를 복원 하시겠습니까?",
"post_purge_confirm": "이 포스트를 폐기 하시겠습니까?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "게시판들을 읽어오는 중입니다.",
"confirm_move": "이동",
"confirm_fork": "분리",
diff --git a/public/language/lt/admin/settings/api.json b/public/language/lt/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/lt/admin/settings/api.json
+++ b/public/language/lt/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/lt/admin/settings/user.json b/public/language/lt/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/lt/admin/settings/user.json
+++ b/public/language/lt/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/lt/error.json b/public/language/lt/error.json
index e01dd5fa1f..3f1535d6d3 100644
--- a/public/language/lt/error.json
+++ b/public/language/lt/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Klaidingas temos ID",
"invalid-pid": "Klaidingas pranešimo ID",
"invalid-uid": "Klaidingas vartotojo ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Klaidingas vartotojo vardas",
"invalid-email": "Klaidingas el. pašto adresas",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/lt/topic.json b/public/language/lt/topic.json
index 7d0b47f37b..a6c2f24fe4 100644
--- a/public/language/lt/topic.json
+++ b/public/language/lt/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Ar jūs tikrai norite ištrinti šį įrašą?",
"post_restore_confirm": "Ar jūs tikrai norite atkurti šį įrašą?",
"post_purge_confirm": "Ar tikrai norite išvalyti šį pranešimą?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Įkeliamos kategorijos",
"confirm_move": "Perkelti",
"confirm_fork": "Išskaidyti",
diff --git a/public/language/lv/admin/settings/api.json b/public/language/lv/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/lv/admin/settings/api.json
+++ b/public/language/lv/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/lv/admin/settings/user.json b/public/language/lv/admin/settings/user.json
index 748db1fcfd..58b8fbfb5f 100644
--- a/public/language/lv/admin/settings/user.json
+++ b/public/language/lv/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Atļaut konta izdzēšanu",
"hide-fullname": "Slēpt vārdu un uzvārdu no lietotājiem",
"hide-email": "Slēpt e-pasta adresi no lietotājiem",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Tēmas",
"disable-user-skins": "Neļaut lietotājiem izvēlēties pielāgotu ādiņu",
"account-protection": "Konta aizsardzība",
diff --git a/public/language/lv/error.json b/public/language/lv/error.json
index 4fc5bfd9e0..a206838a88 100644
--- a/public/language/lv/error.json
+++ b/public/language/lv/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Nederīgs temata ID",
"invalid-pid": "Nederīgs raksta ID",
"invalid-uid": "Nederīgs lietotāja ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nederīgs lietotājvārds",
"invalid-email": "Nederīga e-pasta adrese",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/lv/topic.json b/public/language/lv/topic.json
index dc6f01b54b..c7065f7216 100644
--- a/public/language/lv/topic.json
+++ b/public/language/lv/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Vai tiešām vēlies izdzēst šo rakstu?",
"post_restore_confirm": "Vai tiešām vēlies atjaunot šo rakstu?",
"post_purge_confirm": "Vai tiešām vēlies iztīrīt šo rakstu?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Ielādē kategorijas",
"confirm_move": "Pārvietot",
"confirm_fork": "Nozarot",
diff --git a/public/language/ms/admin/settings/api.json b/public/language/ms/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/ms/admin/settings/api.json
+++ b/public/language/ms/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/ms/admin/settings/user.json b/public/language/ms/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/ms/admin/settings/user.json
+++ b/public/language/ms/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/ms/error.json b/public/language/ms/error.json
index 74ae964ae1..2844037466 100644
--- a/public/language/ms/error.json
+++ b/public/language/ms/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Topik ID Tak Sah",
"invalid-pid": "Kiriman ID Tak Sah",
"invalid-uid": "ID Pengguna Tak Sah",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nama Pengguna Tak Sah",
"invalid-email": "Emel Tak Sah",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/ms/topic.json b/public/language/ms/topic.json
index 5f7f6257f1..2cce10e948 100644
--- a/public/language/ms/topic.json
+++ b/public/language/ms/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Adakah anda pasti untuk memadam kiriman ini?",
"post_restore_confirm": "Adakah anda pasti untuk memulihkan kiriman ini?",
"post_purge_confirm": "Adakah anda pasti untuk singkirkan kiriman ini?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Memuatkan kategori",
"confirm_move": "Pindahkan",
"confirm_fork": "Salin",
diff --git a/public/language/nb/admin/settings/api.json b/public/language/nb/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/nb/admin/settings/api.json
+++ b/public/language/nb/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/nb/admin/settings/user.json b/public/language/nb/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/nb/admin/settings/user.json
+++ b/public/language/nb/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/nb/error.json b/public/language/nb/error.json
index 51154d7dfc..7a6406736a 100644
--- a/public/language/nb/error.json
+++ b/public/language/nb/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Ugyldig emne-ID",
"invalid-pid": "Ugyldig innlegg-ID",
"invalid-uid": "Ugyldig bruker-ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Ugyldig brukernavn",
"invalid-email": "Ugyldig e-post",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/nb/topic.json b/public/language/nb/topic.json
index c59a5424f4..ae86a6dba1 100644
--- a/public/language/nb/topic.json
+++ b/public/language/nb/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Er du sikker på at du vil slette dette innlegget?",
"post_restore_confirm": "Er du sikker på at du vil gjenopprette dette innlegget?",
"post_purge_confirm": "Er du sikker på at du vil renske dette innlegget?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Laster kategorier",
"confirm_move": "Flytt",
"confirm_fork": "Forgren",
diff --git a/public/language/nl/admin/settings/api.json b/public/language/nl/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/nl/admin/settings/api.json
+++ b/public/language/nl/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/nl/admin/settings/user.json b/public/language/nl/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/nl/admin/settings/user.json
+++ b/public/language/nl/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/nl/error.json b/public/language/nl/error.json
index adbb61e047..6dab2ab6ee 100644
--- a/public/language/nl/error.json
+++ b/public/language/nl/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Ongeldig onderwerp ID",
"invalid-pid": "Ongeldig berichtkenmerk",
"invalid-uid": "Ongeldig gebruikerskenmerk",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Ongeldige gebruikersnaam",
"invalid-email": "Ongeldig e-mailadres",
"invalid-fullname": "Ongeldige volledige naam",
diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json
index 48791248da..df807acdbf 100644
--- a/public/language/nl/topic.json
+++ b/public/language/nl/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Is het absoluut de bedoeling dit bericht te verwijderen?",
"post_restore_confirm": "Is het de bedoeling dit bericht te herstellen?",
"post_purge_confirm": "Is het absoluut zeker dat dit bericht volledig verwijderd kan worden?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Categorieën laden",
"confirm_move": "Verplaatsen",
"confirm_fork": "Splits",
diff --git a/public/language/pl/admin/settings/api.json b/public/language/pl/admin/settings/api.json
index c0c9758870..13742ccb6b 100644
--- a/public/language/pl/admin/settings/api.json
+++ b/public/language/pl/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokeny",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Kliknij tutaj, aby zobaczyć pełną specyfikację API",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "ID Użytkownika",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Opis",
diff --git a/public/language/pl/admin/settings/user.json b/public/language/pl/admin/settings/user.json
index 1a1e1388e8..8c02686702 100644
--- a/public/language/pl/admin/settings/user.json
+++ b/public/language/pl/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Zezwalaj na usunięcie konta",
"hide-fullname": "Ukrywaj pełne imię i nazwisko przed innymi użytkownikami",
"hide-email": "Ukryj adresy e-mail użytkowników",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Motywy",
"disable-user-skins": "Nie zezwalaj użytkownikom na wybranie niestandardowej skórki",
"account-protection": "Ochrona konta",
diff --git a/public/language/pl/error.json b/public/language/pl/error.json
index 8d235636e0..9d9d0264b8 100644
--- a/public/language/pl/error.json
+++ b/public/language/pl/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Nieprawidłowy ID tematu",
"invalid-pid": "Nieprawidłowy ID posta",
"invalid-uid": "Nieprawidłowy ID użytkownika",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nieprawidłowy login",
"invalid-email": "Nieprawidłowy adres e-mail",
"invalid-fullname": "Nieprawidłowa nazwa",
diff --git a/public/language/pl/topic.json b/public/language/pl/topic.json
index 57f5ee1a1e..6975bc7338 100644
--- a/public/language/pl/topic.json
+++ b/public/language/pl/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Czy na pewno chcesz usunąć ten post?",
"post_restore_confirm": "Czy na pewno chcesz przywrócić ten post?",
"post_purge_confirm": "Czy na pewno chcesz wyczyścić ten post?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Ładowanie kategorii",
"confirm_move": "Przenieś",
"confirm_fork": "Skopiuj",
diff --git a/public/language/pt-BR/admin/settings/api.json b/public/language/pt-BR/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/pt-BR/admin/settings/api.json
+++ b/public/language/pt-BR/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/pt-BR/admin/settings/user.json b/public/language/pt-BR/admin/settings/user.json
index 5882ca355b..1af9f279f2 100644
--- a/public/language/pt-BR/admin/settings/user.json
+++ b/public/language/pt-BR/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Permitir exclusão de conta",
"hide-fullname": "Esconder nome completo de outros usuários",
"hide-email": "Esconder e-mail de outros usuários",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Temas",
"disable-user-skins": "Impedir usuários de escolherem um tema diferente",
"account-protection": "Proteção de Conta",
diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json
index 106dc2b350..8659e2babe 100644
--- a/public/language/pt-BR/error.json
+++ b/public/language/pt-BR/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID de Tópico Inválido",
"invalid-pid": "ID de Post Inválido",
"invalid-uid": "ID de Usuário Inválido",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nome de Usuário Inválido",
"invalid-email": "Email Inválido",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/pt-BR/topic.json b/public/language/pt-BR/topic.json
index 7ae84e2309..50198172b4 100644
--- a/public/language/pt-BR/topic.json
+++ b/public/language/pt-BR/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Tem certeza que deseja deletar este post?",
"post_restore_confirm": "Tem certeza que deseja restaurar este post?",
"post_purge_confirm": "Tem certeza que deseja expurgar este post?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Carregando Categorias",
"confirm_move": "Mover",
"confirm_fork": "Ramificar",
diff --git a/public/language/pt-PT/admin/settings/api.json b/public/language/pt-PT/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/pt-PT/admin/settings/api.json
+++ b/public/language/pt-PT/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/pt-PT/admin/settings/user.json b/public/language/pt-PT/admin/settings/user.json
index 33edff1b13..41ebad0764 100644
--- a/public/language/pt-PT/admin/settings/user.json
+++ b/public/language/pt-PT/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Permitir eliminação da conta",
"hide-fullname": "Esconder o nome completo dos utilizadores",
"hide-email": "Esconder o e-mail dos utilizadores",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Temas",
"disable-user-skins": "Impedir utilizadores de escolherem uma máscara personalizada",
"account-protection": "Proteção de Conta",
diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json
index 08e100217d..68cee9c04d 100644
--- a/public/language/pt-PT/error.json
+++ b/public/language/pt-PT/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID de Tópico Inválido",
"invalid-pid": "ID de Publicação Inválido",
"invalid-uid": "ID de Utilizador Inválido",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Utilizador Inválido",
"invalid-email": "E-mail Inválido",
"invalid-fullname": "Nome Completo Inválido",
diff --git a/public/language/pt-PT/topic.json b/public/language/pt-PT/topic.json
index 7599763939..968100b863 100644
--- a/public/language/pt-PT/topic.json
+++ b/public/language/pt-PT/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Tens a certeza que desejas eliminar esta publicação?",
"post_restore_confirm": "Tens a certeza que desejas restaurar esta publicação?",
"post_purge_confirm": "Tens a certeza que queres eliminar esta publicação?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Carregando Categorias",
"confirm_move": "Mover",
"confirm_fork": "Clonar",
diff --git a/public/language/ro/admin/settings/api.json b/public/language/ro/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/ro/admin/settings/api.json
+++ b/public/language/ro/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/ro/admin/settings/user.json b/public/language/ro/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/ro/admin/settings/user.json
+++ b/public/language/ro/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/ro/error.json b/public/language/ro/error.json
index 67321931c2..6b3a0ce877 100644
--- a/public/language/ro/error.json
+++ b/public/language/ro/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID Subiect Invalid",
"invalid-pid": "ID Mesaj Invalid",
"invalid-uid": "ID Utilizator Invalid",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Utilizator Invalid",
"invalid-email": "Email Invalid",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/ro/topic.json b/public/language/ro/topic.json
index dd1844b7b4..29468abb41 100644
--- a/public/language/ro/topic.json
+++ b/public/language/ro/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Ești sigur că vrei să ștergi acest mesaj?",
"post_restore_confirm": "Esti sigur că vrei să restaurezi acest mesaj?",
"post_purge_confirm": "Ești sigur că vrei să cureți acest mesaj?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Se Încarcă Categoriile",
"confirm_move": "Mută",
"confirm_fork": "Bifurcă",
diff --git a/public/language/ru/admin/settings/api.json b/public/language/ru/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/ru/admin/settings/api.json
+++ b/public/language/ru/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/ru/admin/settings/user.json b/public/language/ru/admin/settings/user.json
index 11a91f4fd3..b6f697eb8b 100644
--- a/public/language/ru/admin/settings/user.json
+++ b/public/language/ru/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Разрешить удалять учётную запись",
"hide-fullname": "Скрывать полное имя от других пользователей",
"hide-email": "Скрывать e-mail от других пользователей",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Оформление",
"disable-user-skins": "Запретить пользователям выбирать стиль темы",
"account-protection": "Защита учётных записей",
diff --git a/public/language/ru/error.json b/public/language/ru/error.json
index bda7eef1e1..073729fefa 100644
--- a/public/language/ru/error.json
+++ b/public/language/ru/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Неправильный ID темы",
"invalid-pid": "Неправильный ID сообщения",
"invalid-uid": "Неправильный ID пользователя",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Неправильное имя пользователя",
"invalid-email": "Неправильный адрес электронной почты",
"invalid-fullname": "Некорректное полное имя",
diff --git a/public/language/ru/topic.json b/public/language/ru/topic.json
index 40f5bd5934..8bd0e00f81 100644
--- a/public/language/ru/topic.json
+++ b/public/language/ru/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Вы уверены, что хотите удалить это сообщение?",
"post_restore_confirm": "Вы уверены, что хотите восстановить это сообщение?",
"post_purge_confirm": "Вы уверены, что хотите стереть это сообщение?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Загружаем категории",
"confirm_move": "Перенести",
"confirm_fork": "Разделить",
diff --git a/public/language/rw/admin/settings/api.json b/public/language/rw/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/rw/admin/settings/api.json
+++ b/public/language/rw/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/rw/admin/settings/user.json b/public/language/rw/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/rw/admin/settings/user.json
+++ b/public/language/rw/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/rw/error.json b/public/language/rw/error.json
index 8343dfe380..9dc57e058e 100644
--- a/public/language/rw/error.json
+++ b/public/language/rw/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Nimero y'Ikiganiro Ntiyemewe",
"invalid-pid": "Nimero y'Icyashyizweho Ntiyemewe",
"invalid-uid": "Nimero y'Umuntu Ntiyemewe",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Izina Ntiryemewe",
"invalid-email": "Email Ntiyemewe",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json
index fb15f3a347..62fc31c281 100644
--- a/public/language/rw/topic.json
+++ b/public/language/rw/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Wiringiye neza ko ushaka gukuraho iki kiganiro?",
"post_restore_confirm": "Wiringiye neza ko ushaka kugarura iki kiganiro? ",
"post_purge_confirm": "Wiringiye neza ko ushaka gusibangaya iki kiganiro?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Ibyiciro Biraje",
"confirm_move": "Imura",
"confirm_fork": "Gabanyaho",
diff --git a/public/language/sc/admin/settings/api.json b/public/language/sc/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/sc/admin/settings/api.json
+++ b/public/language/sc/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/sc/admin/settings/user.json b/public/language/sc/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/sc/admin/settings/user.json
+++ b/public/language/sc/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/sc/error.json b/public/language/sc/error.json
index 82ae136078..c50e9c05cd 100644
--- a/public/language/sc/error.json
+++ b/public/language/sc/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Invalid Topic ID",
"invalid-pid": "Invalid Post ID",
"invalid-uid": "Invalid User ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Invalid Username",
"invalid-email": "Invalid Email",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/sc/topic.json b/public/language/sc/topic.json
index 9c4fd5ce6d..1068121b5e 100644
--- a/public/language/sc/topic.json
+++ b/public/language/sc/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Are you sure you want to delete this post?",
"post_restore_confirm": "Are you sure you want to restore this post?",
"post_purge_confirm": "Are you sure you want to purge this post?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Carrighende Crezes",
"confirm_move": "Move",
"confirm_fork": "Partzi",
diff --git a/public/language/sk/admin/settings/api.json b/public/language/sk/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/sk/admin/settings/api.json
+++ b/public/language/sk/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/sk/admin/settings/user.json b/public/language/sk/admin/settings/user.json
index 7c4d0a5c1e..e35c29d33f 100644
--- a/public/language/sk/admin/settings/user.json
+++ b/public/language/sk/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Povoliť zmazanie účtu",
"hide-fullname": "Skryť meno pred používateľom",
"hide-email": "Skryť e-mail pre používateľmi",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Vzhľady",
"disable-user-skins": "Zabrániť používateľovi vo výbere vlastného vzhľadu",
"account-protection": "Ochrana účtu",
diff --git a/public/language/sk/error.json b/public/language/sk/error.json
index a03c41a3be..ab126c2bb8 100644
--- a/public/language/sk/error.json
+++ b/public/language/sk/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Neplatné ID témy",
"invalid-pid": "Neplatné ID príspevku",
"invalid-uid": "Nesprávne ID užívateľa",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Nesprávne používateľské meno",
"invalid-email": "Nesprávny e-mail",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/sk/topic.json b/public/language/sk/topic.json
index d63f25d323..055ecf9a24 100644
--- a/public/language/sk/topic.json
+++ b/public/language/sk/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Ste si istý, že chcete odstrániť tento príspevok?",
"post_restore_confirm": "Ste si istí, že chcete obnoviť tento príspevok?",
"post_purge_confirm": "Ste si istý že chcete naozaj vyčistiť tento príspevok?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Načítanie kategórií",
"confirm_move": "Presunúť",
"confirm_fork": "Rozdeliť",
diff --git a/public/language/sl/admin/settings/api.json b/public/language/sl/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/sl/admin/settings/api.json
+++ b/public/language/sl/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/sl/admin/settings/user.json b/public/language/sl/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/sl/admin/settings/user.json
+++ b/public/language/sl/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/sl/error.json b/public/language/sl/error.json
index ff37d6be0a..a90966f1d1 100644
--- a/public/language/sl/error.json
+++ b/public/language/sl/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Napačen ID teme",
"invalid-pid": "Napačen ID objave",
"invalid-uid": "Napačen ID uporabnika",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Napačno uporabniško ime",
"invalid-email": "Napačen elektronski naslov",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/sl/topic.json b/public/language/sl/topic.json
index 3ed9160fd8..50a1770a2f 100644
--- a/public/language/sl/topic.json
+++ b/public/language/sl/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Ste prepričani, da želite izbrisati to objavo?",
"post_restore_confirm": "Ste prepričani, da želite razveljaviti to objavo?",
"post_purge_confirm": "Ste prepričani, da želite očistiti to objavo?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Nalagam kategorije",
"confirm_move": "Premakni",
"confirm_fork": "Razcepi",
diff --git a/public/language/sr/admin/settings/api.json b/public/language/sr/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/sr/admin/settings/api.json
+++ b/public/language/sr/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/sr/admin/settings/user.json b/public/language/sr/admin/settings/user.json
index 81383b8a0f..d4d6e440cc 100644
--- a/public/language/sr/admin/settings/user.json
+++ b/public/language/sr/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Dozvoli brisanje naloga",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Teme",
"disable-user-skins": "Onemogući korisnike da izaberu određenu temu",
"account-protection": "Začtita naloga",
diff --git a/public/language/sr/error.json b/public/language/sr/error.json
index b1dc14b4ae..1ce2004430 100644
--- a/public/language/sr/error.json
+++ b/public/language/sr/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Неисправан ID теме",
"invalid-pid": "Неисправан ID поруке",
"invalid-uid": "Неисправан ИД корисника",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Неисправно корисничко име",
"invalid-email": "Неисправна е-пошта",
"invalid-fullname": "Неисправно пуно име",
diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json
index affe0651c4..7d0c11555f 100644
--- a/public/language/sr/topic.json
+++ b/public/language/sr/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Да ли сте сигурни да желите да избришете ову поруку?",
"post_restore_confirm": "Да ли сте сигурни да желите да обновите ову поруку?",
"post_purge_confirm": "Да ли сте сигурни да желите да очистите овај пост?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Учитавање категорија",
"confirm_move": "Премести",
"confirm_fork": "Рачвај",
diff --git a/public/language/sv/admin/settings/api.json b/public/language/sv/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/sv/admin/settings/api.json
+++ b/public/language/sv/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/sv/admin/settings/user.json b/public/language/sv/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/sv/admin/settings/user.json
+++ b/public/language/sv/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/sv/error.json b/public/language/sv/error.json
index 1977478c68..8a41d072b9 100644
--- a/public/language/sv/error.json
+++ b/public/language/sv/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Ogiltigt id för ämne",
"invalid-pid": "Ogiltigt id för inlägg",
"invalid-uid": "Ogiltigt id för användare",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Ogiltigt användarnamn",
"invalid-email": "Ogiltig epostadress",
"invalid-fullname": "Ogiltigt namn",
diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json
index 197d235d3e..aac9c5dc61 100644
--- a/public/language/sv/topic.json
+++ b/public/language/sv/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Är du säker på att du vill ta bort det här inlägget?",
"post_restore_confirm": "Är du säker på att du vill återställa det här inlägget?",
"post_purge_confirm": "Är du säker att du vill rensa bort det här inlägget?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Laddar kategorier",
"confirm_move": "Flytta",
"confirm_fork": "Grena",
diff --git a/public/language/th/admin/settings/api.json b/public/language/th/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/th/admin/settings/api.json
+++ b/public/language/th/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/th/admin/settings/user.json b/public/language/th/admin/settings/user.json
index f5c770fe26..04814386d3 100644
--- a/public/language/th/admin/settings/user.json
+++ b/public/language/th/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "ธีม",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/th/error.json b/public/language/th/error.json
index cbab459516..4d35c24254 100644
--- a/public/language/th/error.json
+++ b/public/language/th/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Topic ID ไม่ถูกต้อง",
"invalid-pid": "Post ID ไม่ถูกต้อง",
"invalid-uid": "User ID ไม่ถูกต้อง",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "ชื่อผู้ใช้ไม่ถูกต้อง",
"invalid-email": "อีเมลไม่ถูกต้อง",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/th/topic.json b/public/language/th/topic.json
index 57c29e9cf0..af5b612a0f 100644
--- a/public/language/th/topic.json
+++ b/public/language/th/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "คุณแน่ใจแล้วใช่ไหมว่าต้องการลบโพสต์นี้",
"post_restore_confirm": "คุณแน่ใจแล้วใช้ไหมว่าต้องการกู้คืนโพสต์นี้",
"post_purge_confirm": "คุณแน่ใจแล้วใช่ไหมว่าต้องการล้างโพสต์นี้",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "กำลังโหลดหมวดหมู่",
"confirm_move": "ย้าย",
"confirm_fork": "แยก",
diff --git a/public/language/tr/admin/settings/advanced.json b/public/language/tr/admin/settings/advanced.json
index 28e2766d79..7cdf04e608 100644
--- a/public/language/tr/admin/settings/advanced.json
+++ b/public/language/tr/admin/settings/advanced.json
@@ -35,7 +35,7 @@
"sockets.delay": "Yeniden Bağlanma Gecikmesi",
"analytics.settings": "Analitik Ayarlar",
- "analytics.max-cache": "Analytics Cache Max Value",
+ "analytics.max-cache": "Analitik Önbellek Maksimum Değeri",
"analytics.max-cache-help": "On high-traffic installs, the cache could be exhausted continuously if there are more concurrent active users than the Max Cache value. (Restart required)",
"compression.settings": "Sıkıştırma Ayarları",
"compression.enable": "Sıkıştırmayı Aktifleştir",
diff --git a/public/language/tr/admin/settings/api.json b/public/language/tr/admin/settings/api.json
index ede02cd927..d6144673cd 100644
--- a/public/language/tr/admin/settings/api.json
+++ b/public/language/tr/admin/settings/api.json
@@ -1,12 +1,16 @@
{
- "tokens": "Tokens",
- "lead-text": "From this page you can configure access to the Write API in NodeBB.",
+ "tokens": "Jetonlar (Tokens)",
+ "settings": "Settings",
+ "lead-text": "Bu sayfadan NodeBB'deki \"Write API\"e erişimi yapılandırabilirsiniz.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Tüm API özeliklerine erişmek için buraya tıklayın. ",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "Kullanıcı ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Açıklama",
"no-description": "Hiçbir açıklama belirtilmemiş.",
- "token-on-save": "Token will be generated once form is saved"
+ "token-on-save": "Form kaydedildikten sonra bir jeton oluşturulacak"
}
\ No newline at end of file
diff --git a/public/language/tr/admin/settings/user.json b/public/language/tr/admin/settings/user.json
index e3f1c18353..35ef948bf5 100644
--- a/public/language/tr/admin/settings/user.json
+++ b/public/language/tr/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Hesap silmeye izin ver",
"hide-fullname": "Kullanıcı adını gizle",
"hide-email": "E-posta adresini kullanıcılardan gizle",
+ "show-fullname-as-displayname": "Eğer girilmişse kullanıcının tam ismini görüntülenen isim olarak değiştir",
"themes": "Temalar",
"disable-user-skins": "Kullanıcıların özel bir deri seçmesini engelle",
"account-protection": "Hesap Koruma",
diff --git a/public/language/tr/category.json b/public/language/tr/category.json
index 261815c8ac..fb33594c97 100644
--- a/public/language/tr/category.json
+++ b/public/language/tr/category.json
@@ -1,7 +1,7 @@
{
"category": "Kategori",
"subcategories": "Alt kategoriler",
- "new_topic_button": "Yeni Konu Aç",
+ "new_topic_button": "Yeni Başlık",
"guest-login-post": "İleti göndermek için giriş yapın",
"no_topics": " Bu kategoride hiç konu yok.
Yeni bir konu oluşturmak istemez misiniz?",
"browsing": "gözden geçiriliyor",
diff --git a/public/language/tr/error.json b/public/language/tr/error.json
index 0457c987cc..9d70592675 100644
--- a/public/language/tr/error.json
+++ b/public/language/tr/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Geçersiz Konu ID",
"invalid-pid": "Geçersiz İleti ID",
"invalid-uid": "Geçersiz Kullanıcı ID",
+ "invalid-date": "Geçerli bir tarih girmelisiniz.",
"invalid-username": "Geçersiz Kullanıcı İsmi",
"invalid-email": "Geçersiz E-posta",
"invalid-fullname": "Hatalı İsim",
diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json
index 83132c1e14..c6faa84f0c 100644
--- a/public/language/tr/notifications.json
+++ b/public/language/tr/notifications.json
@@ -31,9 +31,9 @@
"user_flagged_user": "%1 şu kullanıcının profilini şikayet etti: (%2)",
"user_flagged_user_dual": "%1 ve %2 şu kullanıcının profilini şikayet etti: (%3)",
"user_flagged_user_multiple": "%1 ve %2 kişi daha şu kullanıcının profilini şikayet etti: (%3)",
- "user_posted_to": "%1 şu konudaki iletinizi cevapladı: %2 ",
- "user_posted_to_dual": "%1 ve %2 şu konudaki iletinizi cevapladı: %3",
- "user_posted_to_multiple": "%1 ve %2 kişi daha şu konudaki iletinizi cevapladı: %3",
+ "user_posted_to": "%1 şu konuya bir ileti yazdı: %2 ",
+ "user_posted_to_dual": "%1 ve %2 şu konuya ileti yazdılar: %3",
+ "user_posted_to_multiple": "%1 ve %2 kişi daha şu konuya ileti yazdılar: %3",
"user_posted_topic": "%1 şu yeni konuyu oluşturdu: %2",
"user_edited_post": "%1 şu konudaki bir iletiyi değiştirdi: %2",
"user_started_following_you": "%1 sizi takip etmeye başladı.",
diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json
index cb7a6e3cff..0816900cb2 100644
--- a/public/language/tr/topic.json
+++ b/public/language/tr/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Bu iletiyi gerçekten silmek istediğinize emin misiniz?",
"post_restore_confirm": "Bu iletiyi gerçekten geri getirmek istiyor musunuz?",
"post_purge_confirm": "Bu iletiyi temizlemek istediğinize emin misiniz?",
+ "pin-modal-expiry": "Sona erme tarihi",
+ "pin-modal-help": "Sabitlenen konular için bir bitiş tarihi belirleyebilirsiniz. Eğer bu tarihi boş bırakırsanız, konular siz sabitliğini kaldırana kadar sabitlenmiş olarak kalır.",
"load_categories": "Kategoriler Yükleniyor",
"confirm_move": "Taşı",
"confirm_fork": "Ayır",
diff --git a/public/language/uk/admin/settings/api.json b/public/language/uk/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/uk/admin/settings/api.json
+++ b/public/language/uk/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/uk/admin/settings/user.json b/public/language/uk/admin/settings/user.json
index 8d4cca735a..7594e7cee7 100644
--- a/public/language/uk/admin/settings/user.json
+++ b/public/language/uk/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Дозволити видалення акаунту",
"hide-fullname": "Приховати повне ім'я від користувачів",
"hide-email": "Приховати електронну пошту від користувачів",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Теми",
"disable-user-skins": "Заборонити користувачам обирати стиль сайту",
"account-protection": "Захист акаунту",
diff --git a/public/language/uk/error.json b/public/language/uk/error.json
index a730cd726d..b9a542a5ec 100644
--- a/public/language/uk/error.json
+++ b/public/language/uk/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "Невірний ID теми",
"invalid-pid": "Невірний ID поста",
"invalid-uid": "Невірний ID користувача",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Невірне ім'я користувача",
"invalid-email": "Невірна електронна адреса",
"invalid-fullname": "Невірне повне ім'я",
diff --git a/public/language/uk/topic.json b/public/language/uk/topic.json
index bc5f78287f..afc79fc17f 100644
--- a/public/language/uk/topic.json
+++ b/public/language/uk/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Ви точно бажаєте видалити цей пост?",
"post_restore_confirm": "Ви точно бажаєте відновити цей пост?",
"post_purge_confirm": "Ви точно бажаєте стерти цей пост?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Завантаження категорій",
"confirm_move": "Перемістити",
"confirm_fork": "Відгалужити",
diff --git a/public/language/vi/admin/settings/api.json b/public/language/vi/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/vi/admin/settings/api.json
+++ b/public/language/vi/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/vi/admin/settings/user.json b/public/language/vi/admin/settings/user.json
index e05aa5e3ed..48be13b75e 100644
--- a/public/language/vi/admin/settings/user.json
+++ b/public/language/vi/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "Allow account deletion",
"hide-fullname": "Hide fullname from users",
"hide-email": "Hide email from users",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "Themes",
"disable-user-skins": "Prevent users from choosing a custom skin",
"account-protection": "Account Protection",
diff --git a/public/language/vi/error.json b/public/language/vi/error.json
index 07383c539d..458760585f 100644
--- a/public/language/vi/error.json
+++ b/public/language/vi/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "ID chủ đề không hợp lệ",
"invalid-pid": "ID bài viết không hợp lệ",
"invalid-uid": "ID tài khoản không hợp lệ",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "Tên đăng nhập không hợp lệ",
"invalid-email": "Email không hợp lệ",
"invalid-fullname": "Invalid Fullname",
diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json
index 65998bedc5..a4115ca2c5 100644
--- a/public/language/vi/topic.json
+++ b/public/language/vi/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "Bạn có chắc là muốn xóa bài gửi này không?",
"post_restore_confirm": "Bạn có chắc là muốn phục hồi bài gửi này không?",
"post_purge_confirm": "Bạn có chắc muốn xóa hẳn bài này?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "Đang tải các phần mục",
"confirm_move": "Di chuyển",
"confirm_fork": "Tạo bảo sao",
diff --git a/public/language/zh-CN/admin/settings/api.json b/public/language/zh-CN/admin/settings/api.json
index d872ac0a5d..0da117b4f0 100644
--- a/public/language/zh-CN/admin/settings/api.json
+++ b/public/language/zh-CN/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "令牌",
+ "settings": "Settings",
"lead-text": "从此处,您可以配置对 NodeBB 中 Write API 的访问。",
"intro": "默认情况下,Write API根据用户的会话cookie对用户进行身份验证,但 NodeBB 也支持通过此页面生成的令牌进行身份验证。",
"docs": "单击此处访问完整的 API 规范",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "用户ID",
"uid-help-text": "指定要与此令牌关联的用户ID。如果用户ID是 0, 它将被实危 最高 令牌,可以通过 _uid 参数假定其他用户的身份",
"description": "说明",
diff --git a/public/language/zh-CN/admin/settings/user.json b/public/language/zh-CN/admin/settings/user.json
index a6ead5ef54..78ee3b04fe 100644
--- a/public/language/zh-CN/admin/settings/user.json
+++ b/public/language/zh-CN/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "允许消除帐号",
"hide-fullname": "隐藏用户的全名",
"hide-email": "隐藏用户的电子邮箱",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "主题",
"disable-user-skins": "阻止用户选择自定义皮肤",
"account-protection": "帐号保护",
diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json
index 6d558f52d3..bdb7df459a 100644
--- a/public/language/zh-CN/error.json
+++ b/public/language/zh-CN/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "无效主题 ID",
"invalid-pid": "无效帖子 ID",
"invalid-uid": "无效用户 ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "无效用户名",
"invalid-email": "无效的电子邮箱",
"invalid-fullname": "无效全名",
diff --git a/public/language/zh-CN/topic.json b/public/language/zh-CN/topic.json
index fccddf200c..6580a4cfcf 100644
--- a/public/language/zh-CN/topic.json
+++ b/public/language/zh-CN/topic.json
@@ -6,8 +6,8 @@
"topic_is_deleted": "此主题已被删除!",
"profile": "资料",
"posted_by": "%1 发布",
- "posted_by_guest": "游客发布",
- "chat": "对话",
+ "posted_by_guest": "未登录用户发布",
+ "chat": "聊天",
"notify_me": "此主题有新回复时通知我",
"quote": "引用",
"reply": "回复",
@@ -30,7 +30,7 @@
"locked": "已锁定",
"pinned": "已固定",
"moved": "已移动",
- "moved-from": "Moved from %1",
+ "moved-from": "移自1%版 ",
"copy-ip": "复制IP",
"ban-ip": "封禁IP",
"view-history": "编辑历史",
@@ -80,15 +80,17 @@
"thread_tools.purge_confirm": "确认清除此主题吗?",
"thread_tools.merge_topics": "合并主题",
"thread_tools.merge": "合并",
- "topic_move_success": "This topic will be moved to \"%1\" shortly. Click here to undo.",
- "topic_move_multiple_success": "These topics will be moved to \"%1\" shortly. Click here to undo.",
- "topic_move_all_success": "All topics will be moved to \"%1\" shortly. Click here to undo.",
+ "topic_move_success": "注意:此话题将会被移到1%版,点击此处取消。",
+ "topic_move_multiple_success": "注意:以下话题将会被移到%1版,点击此处取消。",
+ "topic_move_all_success": "注意 :所有话题都将被移到1%版,点击此处取消。",
"topic_move_undone": "撤销主题移动",
"topic_move_posts_success": "此帖子将马上移动。点击此处撤销",
"topic_move_posts_undone": "撤销帖子移动",
"post_delete_confirm": "您确定要删除此回复吗?",
"post_restore_confirm": "您确定要恢复此回复吗?",
"post_purge_confirm": "您确定要清除此回复吗?",
+ "pin-modal-expiry": "失效日期",
+ "pin-modal-help": "你可以在此处选择为置顶话题设置一个失效日期。或者也可以选择不设置,则该话题将会一直被置顶,知道管理员取消置顶。",
"load_categories": "正在载入版块",
"confirm_move": "移动",
"confirm_fork": "分割",
diff --git a/public/language/zh-TW/admin/settings/api.json b/public/language/zh-TW/admin/settings/api.json
index ba7d964a04..50892925f3 100644
--- a/public/language/zh-TW/admin/settings/api.json
+++ b/public/language/zh-TW/admin/settings/api.json
@@ -1,9 +1,13 @@
{
"tokens": "Tokens",
+ "settings": "Settings",
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
"docs": "Click here to access the full API specification",
+ "require-https": "Require API usage via HTTPS only",
+ "require-https-caveat": "Note: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
+
"uid": "User ID",
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is 0, it will be considered a master token, which can assume the identity of other users based on the _uid parameter",
"description": "Description",
diff --git a/public/language/zh-TW/admin/settings/user.json b/public/language/zh-TW/admin/settings/user.json
index 93e87c1fe0..b01a691a39 100644
--- a/public/language/zh-TW/admin/settings/user.json
+++ b/public/language/zh-TW/admin/settings/user.json
@@ -16,6 +16,7 @@
"allow-account-deletion": "允許刪除帳戶",
"hide-fullname": "隱藏使用者的全名",
"hide-email": "隱藏使用者的電子信箱",
+ "show-fullname-as-displayname": "Show user's full name as their display name if available",
"themes": "佈景主題",
"disable-user-skins": "阻止使用者選擇自訂配色",
"account-protection": "帳戶保護",
diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json
index 761c39875a..3bdcf3ba32 100644
--- a/public/language/zh-TW/error.json
+++ b/public/language/zh-TW/error.json
@@ -9,6 +9,7 @@
"invalid-tid": "無效主題 ID",
"invalid-pid": "無效貼文 ID",
"invalid-uid": "無效使用者 ID",
+ "invalid-date": "A valid date must be provided",
"invalid-username": "無效使用者名",
"invalid-email": "無效的電子信箱",
"invalid-fullname": "無效全名",
diff --git a/public/language/zh-TW/topic.json b/public/language/zh-TW/topic.json
index b50e287032..4a28540245 100644
--- a/public/language/zh-TW/topic.json
+++ b/public/language/zh-TW/topic.json
@@ -89,6 +89,8 @@
"post_delete_confirm": "您確定要刪除此回覆嗎?",
"post_restore_confirm": "您確定要恢復此回覆嗎?",
"post_purge_confirm": "您確定要清除此回覆嗎?",
+ "pin-modal-expiry": "Expiration Date",
+ "pin-modal-help": "You can optionally set an expiration date for the pinned topic(s) here. Alternatively, you can leave this field blank to have the topic stay pinned until it is manually unpinned.",
"load_categories": "正在載入版面",
"confirm_move": "移動",
"confirm_fork": "分割",
diff --git a/public/openapi/components/schemas/GroupObject.yaml b/public/openapi/components/schemas/GroupObject.yaml
index dbef43abc3..c2a34509f6 100644
--- a/public/openapi/components/schemas/GroupObject.yaml
+++ b/public/openapi/components/schemas/GroupObject.yaml
@@ -14,6 +14,9 @@ GroupFullObject:
userTitle:
type: number
description: Label text for the user badge
+ userTitleEscaped:
+ type: number
+ description: Same as userTitle but with translation tokens escaped, used to display raw userTitle in group management
userTitleEnabled:
type: number
description:
@@ -122,6 +125,9 @@ GroupDataObject:
userTitle:
type: number
description: Label text for the user badge
+ userTitleEscaped:
+ type: number
+ description: Same as userTitle but with translation tokens escaped, used to display raw userTitle in group management
userTitleEnabled:
type: number
description:
diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml
index fc2da47e12..5ff918cd7f 100644
--- a/public/openapi/components/schemas/PostObject.yaml
+++ b/public/openapi/components/schemas/PostObject.yaml
@@ -34,6 +34,9 @@ PostObject:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml
index 69662e0080..590872e03e 100644
--- a/public/openapi/components/schemas/TopicObject.yaml
+++ b/public/openapi/components/schemas/TopicObject.yaml
@@ -1,159 +1,86 @@
TopicObject:
- type: object
- properties:
- tid:
- type: number
- description: A topic identifier
- uid:
- type: number
- description: A user identifier
- cid:
- type: number
- description: A category identifier
- mainPid:
- type: number
- description: The post id of the first post in this topic (also called the
- "original post")
- title:
- type: string
- slug:
- type: string
- timestamp:
- type: number
- lastposttime:
- type: number
- postcount:
- type: number
- viewcount:
- type: number
- postercount:
- type: number
- teaserPid:
- oneOf:
- - type: number
- - type: string
- nullable: true
- upvotes:
- type: number
- downvotes:
- type: number
- deleted:
- type: number
- locked:
- type: number
- pinned:
- type: number
- description: Whether or not this particular topic is pinned to the top of the
- category
- deleterUid:
- type: number
- titleRaw:
- type: string
- timestampISO:
- type: string
- description: An ISO 8601 formatted date string (complementing `timestamp`)
- lastposttimeISO:
- type: string
- votes:
- type: number
- category:
- type: object
+ allOf:
+ - type: object
properties:
- cid:
- type: number
- description: A category identifier
- name:
- type: string
- slug:
- type: string
- icon:
- type: string
- backgroundImage:
- nullable: true
- type: string
- imageClass:
- nullable: true
- type: string
- bgColor:
- type: string
- color:
- type: string
- disabled:
- type: number
- user:
- type: object
- properties:
- uid:
- type: number
- description: A user identifier
- username:
- type: string
- description: A friendly name for a given user account
- fullname:
- type: string
- userslug:
- type: string
- description: An URL-safe variant of the username (i.e. lower-cased, spaces
- removed, etc.)
- reputation:
- type: number
- postcount:
- type: number
- picture:
- type: string
- nullable: true
- signature:
- type: string
- nullable: true
- banned:
- type: number
- status:
- type: string
- icon:text:
- type: string
- description: A single-letter representation of a username. This is used in the
- auto-generated icon given to users without
- an avatar
- icon:bgColor:
- type: string
- description: A six-character hexadecimal colour code assigned to the user. This
- value is used in conjunction with
- `icon:text` for the user's auto-generated
- icon
- example: "#f44336"
- banned_until_readable:
- type: string
- required:
- - uid
- - username
- - userslug
- - reputation
- - postcount
- - picture
- - signature
- - banned
- - status
- - icon:text
- - icon:bgColor
- - banned_until_readable
- teaser:
- type: object
- properties:
- pid:
- type: number
- uid:
- type: number
- description: A user identifier
- timestamp:
- type: number
tid:
type: number
description: A topic identifier
- content:
+ uid:
+ type: number
+ description: A user identifier
+ cid:
+ type: number
+ description: A category identifier
+ mainPid:
+ type: number
+ description: The post id of the first post in this topic (also called the
+ "original post")
+ title:
+ type: string
+ slug:
+ type: string
+ timestamp:
+ type: number
+ lastposttime:
+ type: number
+ postcount:
+ type: number
+ viewcount:
+ type: number
+ postercount:
+ type: number
+ teaserPid:
+ oneOf:
+ - type: number
+ - type: string
+ nullable: true
+ upvotes:
+ type: number
+ downvotes:
+ type: number
+ deleted:
+ type: number
+ locked:
+ type: number
+ pinned:
+ type: number
+ description: Whether or not this particular topic is pinned to the top of the
+ category
+ deleterUid:
+ type: number
+ titleRaw:
type: string
timestampISO:
type: string
description: An ISO 8601 formatted date string (complementing `timestamp`)
+ lastposttimeISO:
+ type: string
+ votes:
+ type: number
+ category:
+ type: object
+ properties:
+ cid:
+ type: number
+ description: A category identifier
+ name:
+ type: string
+ slug:
+ type: string
+ icon:
+ type: string
+ backgroundImage:
+ nullable: true
+ type: string
+ imageClass:
+ nullable: true
+ type: string
+ bgColor:
+ type: string
+ color:
+ type: string
+ disabled:
+ type: number
user:
type: object
properties:
@@ -163,93 +90,145 @@ TopicObject:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
+ fullname:
+ type: string
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
removed, etc.)
+ reputation:
+ type: number
+ postcount:
+ type: number
picture:
+ type: string
nullable: true
+ signature:
+ type: string
+ nullable: true
+ banned:
+ type: number
+ status:
type: string
icon:text:
type: string
description: A single-letter representation of a username. This is used in the
- auto-generated icon given to users
- without an avatar
+ auto-generated icon given to users without
+ an avatar
icon:bgColor:
type: string
description: A six-character hexadecimal colour code assigned to the user. This
value is used in conjunction with
- `icon:text` for the user's
- auto-generated icon
+ `icon:text` for the user's auto-generated
+ icon
example: "#f44336"
+ banned_until_readable:
+ type: string
+ required:
+ - uid
+ - username
+ - userslug
+ - reputation
+ - postcount
+ - picture
+ - signature
+ - banned
+ - status
+ - icon:text
+ - icon:bgColor
+ - banned_until_readable
+ teaser:
+ type: object
+ properties:
+ pid:
+ type: number
+ uid:
+ type: number
+ description: A user identifier
+ timestamp:
+ type: number
+ tid:
+ type: number
+ description: A topic identifier
+ content:
+ type: string
+ timestampISO:
+ type: string
+ description: An ISO 8601 formatted date string (complementing `timestamp`)
+ user:
+ type: object
+ properties:
+ uid:
+ type: number
+ description: A user identifier
+ username:
+ type: string
+ description: A friendly name for a given user account
+ userslug:
+ type: string
+ description: An URL-safe variant of the username (i.e. lower-cased, spaces
+ removed, etc.)
+ picture:
+ nullable: true
+ type: string
+ icon:text:
+ type: string
+ description: A single-letter representation of a username. This is used in the
+ auto-generated icon given to users
+ without an avatar
+ icon:bgColor:
+ type: string
+ description: A six-character hexadecimal colour code assigned to the user. This
+ value is used in conjunction with
+ `icon:text` for the user's
+ auto-generated icon
+ example: "#f44336"
+ index:
+ type: number
+ nullable: true
+ tags:
+ type: array
+ items:
+ type: object
+ properties:
+ value:
+ type: string
+ valueEscaped:
+ type: string
+ color:
+ type: string
+ bgColor:
+ type: string
+ score:
+ type: number
+ isOwner:
+ type: boolean
+ ignored:
+ type: boolean
+ unread:
+ type: boolean
+ bookmark:
+ nullable: true
+ type: number
+ unreplied:
+ type: boolean
+ icons:
+ type: array
+ items:
+ type: string
+ description: HTML injected into the theme
index:
type: number
- nullable: true
- tags:
- type: array
- items:
- type: object
- properties:
- value:
- type: string
- valueEscaped:
- type: string
- color:
- type: string
- bgColor:
- type: string
- score:
- type: number
- isOwner:
- type: boolean
- ignored:
- type: boolean
- unread:
- type: boolean
- bookmark:
- nullable: true
- type: number
- unreplied:
- type: boolean
- icons:
- type: array
- items:
- type: string
- description: HTML injected into the theme
- index:
- type: number
- thumb:
- type: string
- required:
- - tid
- - uid
- - cid
- - mainPid
- - title
- - slug
- - timestamp
- - lastposttime
- - postcount
- - viewcount
- - teaserPid
- - upvotes
- - downvotes
- - deleted
- - locked
- - pinned
- - deleterUid
- - titleRaw
- - timestampISO
- - lastposttimeISO
- - votes
- - category
- - user
- - teaser
- - tags
- - isOwner
- - ignored
- - unread
- - bookmark
- - unreplied
- - icons
- - index
\ No newline at end of file
+ - type: object
+ description: Optional properties that may or may not be present (except for `tid`, which is always present, and is only here as a hack to pass validation)
+ properties:
+ tid:
+ type: number
+ description: A topic identifier
+ thumb:
+ type: string
+ required:
+ - tid
\ No newline at end of file
diff --git a/public/openapi/components/schemas/UserObj.yaml b/public/openapi/components/schemas/UserObj.yaml
index edcadb150d..fc2837b2cb 100644
--- a/public/openapi/components/schemas/UserObj.yaml
+++ b/public/openapi/components/schemas/UserObj.yaml
@@ -27,6 +27,10 @@ UserObj:
fullname:
type: string
example: Mr. Dragon Fruit Jr.
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
+ example: Dragon Fruit
location:
type: string
example: 'Toronto, Canada'
diff --git a/public/openapi/components/schemas/UserObject.yaml b/public/openapi/components/schemas/UserObject.yaml
index 6c6910a239..2edc402156 100644
--- a/public/openapi/components/schemas/UserObject.yaml
+++ b/public/openapi/components/schemas/UserObject.yaml
@@ -37,6 +37,10 @@ UserObject:
fullname:
type: string
example: Mr. Dragon Fruit Jr.
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
+ example: Dragon Fruit
location:
type: string
example: 'Toronto, Canada'
@@ -227,6 +231,10 @@ UserObjectFull:
fullname:
type: string
example: Mr. Dragon Fruit Jr.
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
+ example: Dragon Fruit
location:
type: string
example: 'Toronto, Canada'
@@ -347,6 +355,37 @@ UserObjectFull:
type: string
moderationNote:
type: string
+ counts:
+ type: object
+ properties:
+ best:
+ type: number
+ blocks:
+ type: number
+ bookmarks:
+ type: number
+ categoriesWatched:
+ type: number
+ downvoted:
+ type: number
+ followers:
+ type: number
+ following:
+ type: number
+ groups:
+ type: number
+ ignored:
+ type: number
+ posts:
+ type: number
+ topics:
+ type: number
+ uploaded:
+ type: number
+ upvoted:
+ type: number
+ watched:
+ type: number
isBlocked:
type: boolean
blocksCount:
@@ -463,6 +502,10 @@ UserObjectSlim:
type: string
description: A friendly name for a given user account
example: Dragon Fruit
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
+ example: Dragon Fruit
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces removed, etc.)
@@ -544,6 +587,10 @@ UserObjectACP:
type: string
description: A friendly name for a given user account
example: Dragon Fruit
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
+ example: Dragon Fruit
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces removed, etc.)
diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml
index 885307aee8..94481e01ba 100644
--- a/public/openapi/read.yaml
+++ b/public/openapi/read.yaml
@@ -168,7 +168,7 @@ paths:
"/api/categories/{cid}/moderators":
$ref: 'read/categories/cid/moderators.yaml'
"/api/topic/{topic_id}/{slug}/{post_index}":
- $ref: 'read/topic/topic_id/slug/post_index.yaml'
+ $ref: 'read/topic/topic_id.yaml'
/api/recent:
$ref: 'read/recent.yaml'
"/api/recent/posts/{term}":
@@ -218,7 +218,7 @@ paths:
/api/top:
$ref: 'read/top.yaml'
"/api/category/{category_id}/{slug}/{topic_index}":
- $ref: 'read/category/category_id/slug/topic_index.yaml'
+ $ref: 'read/category/category_id.yaml'
/api/self:
$ref: 'read/self.yaml'
/api/me:
diff --git a/public/openapi/read/admin/development/info.yaml b/public/openapi/read/admin/development/info.yaml
index 2add1b8d5f..23d277fc3f 100644
--- a/public/openapi/read/admin/development/info.yaml
+++ b/public/openapi/read/admin/development/info.yaml
@@ -61,6 +61,8 @@ get:
- humanReadable
uptime:
type: number
+ uptimeHumanReadable:
+ type: string
cpuUsage:
type: object
properties:
diff --git a/public/openapi/read/admin/manage/digest.yaml b/public/openapi/read/admin/manage/digest.yaml
index 6ddfc471e2..f094a3f7b6 100644
--- a/public/openapi/read/admin/manage/digest.yaml
+++ b/public/openapi/read/admin/manage/digest.yaml
@@ -21,6 +21,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
picture:
nullable: true
type: string
diff --git a/public/openapi/read/admin/manage/groups.yaml b/public/openapi/read/admin/manage/groups.yaml
index 7b7ae1db46..cc93b19469 100644
--- a/public/openapi/read/admin/manage/groups.yaml
+++ b/public/openapi/read/admin/manage/groups.yaml
@@ -30,6 +30,8 @@ get:
type: number
userTitle:
type: string
+ userTitleEscaped:
+ type: string
icon:
type: string
labelColor:
diff --git a/public/openapi/read/categories.yaml b/public/openapi/read/categories.yaml
index cfa3cc9724..723501a410 100644
--- a/public/openapi/read/categories.yaml
+++ b/public/openapi/read/categories.yaml
@@ -150,6 +150,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/read/category/category_id/slug/topic_index.yaml b/public/openapi/read/category/category_id.yaml
similarity index 86%
rename from public/openapi/read/category/category_id/slug/topic_index.yaml
rename to public/openapi/read/category/category_id.yaml
index c0c40714c5..845af7d68a 100644
--- a/public/openapi/read/category/category_id/slug/topic_index.yaml
+++ b/public/openapi/read/category/category_id.yaml
@@ -31,7 +31,7 @@ get:
application/json:
schema:
allOf:
- - $ref: ../../../../components/schemas/CategoryObject.yaml#/CategoryObject
+ - $ref: ../../components/schemas/CategoryObject.yaml#/CategoryObject
- type: object
properties:
tagWhitelist:
@@ -43,11 +43,11 @@ get:
children:
type: array
items:
- $ref: ../../../../components/schemas/CategoryObject.yaml#/CategoryObject
+ $ref: ../../components/schemas/CategoryObject.yaml#/CategoryObject
topics:
type: array
items:
- $ref: ../../../../components/schemas/TopicObject.yaml#/TopicObject
+ $ref: ../../components/schemas/TopicObject.yaml#/TopicObject
nextStart:
type: number
isWatched:
@@ -92,6 +92,6 @@ get:
type: number
reputation:disabled:
type: number
- - $ref: ../../../../components/schemas/Pagination.yaml#/Pagination
- - $ref: ../../../../components/schemas/Breadcrumbs.yaml#/Breadcrumbs
- - $ref: ../../../../components/schemas/CommonProps.yaml#/CommonProps
\ No newline at end of file
+ - $ref: ../../components/schemas/Pagination.yaml#/Pagination
+ - $ref: ../../components/schemas/Breadcrumbs.yaml#/Breadcrumbs
+ - $ref: ../../components/schemas/CommonProps.yaml#/CommonProps
\ No newline at end of file
diff --git a/public/openapi/read/flags/flagId.yaml b/public/openapi/read/flags/flagId.yaml
index aa41da77d5..50be414e56 100644
--- a/public/openapi/read/flags/flagId.yaml
+++ b/public/openapi/read/flags/flagId.yaml
@@ -77,6 +77,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
@@ -160,6 +163,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/read/groups.yaml b/public/openapi/read/groups.yaml
index a73b29864f..bc5847fbb7 100644
--- a/public/openapi/read/groups.yaml
+++ b/public/openapi/read/groups.yaml
@@ -26,6 +26,8 @@ get:
type: number
userTitle:
type: string
+ userTitleEscaped:
+ type: string
icon:
type: string
labelColor:
diff --git a/public/openapi/read/index.yaml b/public/openapi/read/index.yaml
index 4b29a1dc99..33a1daadd9 100644
--- a/public/openapi/read/index.yaml
+++ b/public/openapi/read/index.yaml
@@ -83,6 +83,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
@@ -145,6 +148,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/read/notifications.yaml b/public/openapi/read/notifications.yaml
index 84d3e56c51..59acc0ad62 100644
--- a/public/openapi/read/notifications.yaml
+++ b/public/openapi/read/notifications.yaml
@@ -36,6 +36,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/read/topic/topic_id/slug/post_index.yaml b/public/openapi/read/topic/topic_id.yaml
similarity index 92%
rename from public/openapi/read/topic/topic_id/slug/post_index.yaml
rename to public/openapi/read/topic/topic_id.yaml
index 47e0a60d2e..88081d23dc 100644
--- a/public/openapi/read/topic/topic_id/slug/post_index.yaml
+++ b/public/openapi/read/topic/topic_id.yaml
@@ -89,7 +89,7 @@ get:
tags:
type: array
items:
- $ref: ../../../../components/schemas/TagObject.yaml#/TagObject
+ $ref: ../../components/schemas/TagObject.yaml#/TagObject
posts:
type: array
items:
@@ -137,6 +137,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
@@ -260,6 +263,8 @@ get:
type: number
selfPost:
type: boolean
+ topicOwnerPost:
+ type: boolean
display_edit_tools:
type: boolean
display_delete_tools:
@@ -274,7 +279,7 @@ get:
type: number
description: The flag identifier, if this particular post has been flagged before
category:
- $ref: ../../../../components/schemas/CategoryObject.yaml#/CategoryObject
+ $ref: ../../components/schemas/CategoryObject.yaml#/CategoryObject
tagWhitelist:
type: array
items:
@@ -322,7 +327,7 @@ get:
related:
type: array
items:
- $ref: ../../../../components/schemas/TopicObject.yaml#/TopicObject
+ $ref: ../../components/schemas/TopicObject.yaml#/TopicObject
unreplied:
type: boolean
icons:
@@ -399,7 +404,18 @@ get:
postIndex:
type: number
loggedInUser:
- $ref: ../../../../components/schemas/UserObject.yaml#/UserObject
- - $ref: ../../../../components/schemas/Pagination.yaml#/Pagination
- - $ref: ../../../../components/schemas/Breadcrumbs.yaml#/Breadcrumbs
- - $ref: ../../../../components/schemas/CommonProps.yaml#/CommonProps
\ No newline at end of file
+ $ref: ../../components/schemas/UserObject.yaml#/UserObject
+ - type: object
+ description: Optional properties that may or may not be present (except for `tid`, which is always present, and is only here as a hack to pass validation)
+ properties:
+ tid:
+ type: number
+ description: A topic identifier
+ thumb:
+ type: string
+ description: An uploaded topic thumbnail
+ required:
+ - tid
+ - $ref: ../../components/schemas/Pagination.yaml#/Pagination
+ - $ref: ../../components/schemas/Breadcrumbs.yaml#/Breadcrumbs
+ - $ref: ../../components/schemas/CommonProps.yaml#/CommonProps
\ No newline at end of file
diff --git a/public/openapi/read/unread.yaml b/public/openapi/read/unread.yaml
index 49360647da..a5fe83348e 100644
--- a/public/openapi/read/unread.yaml
+++ b/public/openapi/read/unread.yaml
@@ -109,6 +109,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
fullname:
type: string
userslug:
diff --git a/public/openapi/read/user/userslug/chats/roomid.yaml b/public/openapi/read/user/userslug/chats/roomid.yaml
index 2612848acd..72fc62a125 100644
--- a/public/openapi/read/user/userslug/chats/roomid.yaml
+++ b/public/openapi/read/user/userslug/chats/roomid.yaml
@@ -65,6 +65,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
@@ -115,6 +118,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
picture:
type: string
nullable: true
@@ -171,6 +177,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
@@ -221,6 +230,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
@@ -256,6 +268,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/read/user/userslug/watched.yaml b/public/openapi/read/user/userslug/watched.yaml
index baa79b19e9..9069465791 100644
--- a/public/openapi/read/user/userslug/watched.yaml
+++ b/public/openapi/read/user/userslug/watched.yaml
@@ -29,6 +29,37 @@ get:
type: array
items:
type: string
+ counts:
+ type: object
+ properties:
+ best:
+ type: number
+ blocks:
+ type: number
+ bookmarks:
+ type: number
+ categoriesWatched:
+ type: number
+ downvoted:
+ type: number
+ followers:
+ type: number
+ following:
+ type: number
+ groups:
+ type: number
+ ignored:
+ type: number
+ posts:
+ type: number
+ topics:
+ type: number
+ uploaded:
+ type: number
+ upvoted:
+ type: number
+ watched:
+ type: number
isBlocked:
type: boolean
blocksCount:
diff --git a/public/openapi/read/users.yaml b/public/openapi/read/users.yaml
index a591bd61a5..160aa927d0 100644
--- a/public/openapi/read/users.yaml
+++ b/public/openapi/read/users.yaml
@@ -38,6 +38,9 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ description: This is either username or fullname depending on forum and user settings
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js
index a64a8d88c5..792560bf2b 100644
--- a/public/src/admin/manage/group.js
+++ b/public/src/admin/manage/group.js
@@ -27,7 +27,7 @@ define('admin/manage/group', [
memberList.init('admin/manage/group');
changeGroupUserTitle.on('keyup', function () {
- groupLabelPreviewText.text(changeGroupUserTitle.val());
+ groupLabelPreviewText.translateText(changeGroupUserTitle.val());
});
changeGroupLabelColor.on('keyup input', function () {
diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js
index 7bf9137d19..424d12881e 100644
--- a/public/src/admin/manage/privileges.js
+++ b/public/src/admin/manage/privileges.js
@@ -3,10 +3,10 @@
define('admin/manage/privileges', [
'autocomplete',
'translator',
- 'benchpress',
'categorySelector',
'mousetrap',
-], function (autocomplete, translator, Benchpress, categorySelector, mousetrap) {
+ 'admin/modules/checkboxRowSelector',
+], function (autocomplete, translator, categorySelector, mousetrap, checkboxRowSelector) {
var Privileges = {};
var cid;
@@ -14,6 +14,8 @@ define('admin/manage/privileges', [
Privileges.init = function () {
cid = isNaN(parseInt(ajaxify.data.selectedCategory.cid, 10)) ? 'admin' : ajaxify.data.selectedCategory.cid;
+ checkboxRowSelector.init('.privilege-table-container');
+
categorySelector.init($('[component="category-selector"]'), function (category) {
cid = parseInt(category.cid, 10);
cid = isNaN(cid) ? 'admin' : cid;
@@ -52,12 +54,14 @@ define('admin/manage/privileges', [
wrapperEl.attr('data-delta', delta);
Privileges.exposeAssumedPrivileges();
}
+ checkboxRowSelector.updateState(checkboxEl);
} else {
app.alertError('[[error:invalid-data]]');
}
});
Privileges.exposeAssumedPrivileges();
+ checkboxRowSelector.updateAll();
Privileges.addEvents(); // events with confirmation modals
};
@@ -145,6 +149,7 @@ define('admin/manage/privileges', [
}, function (html) {
$('.privilege-table-container').html(html);
Privileges.exposeAssumedPrivileges();
+ checkboxRowSelector.updateAll();
hightlightRowByDataAttr('data-group-name', groupToHighlight);
});
@@ -158,7 +163,7 @@ define('admin/manage/privileges', [
this arrangement in the table
*/
var privs = [];
- $('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]').parent().each(function (idx, el) {
+ $('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]:not(.checkbox-helper)').parent().each(function (idx, el) {
if ($(el).find('input').prop('checked')) {
privs.push(el.getAttribute('data-privilege'));
}
diff --git a/public/src/admin/modules/checkboxRowSelector.js b/public/src/admin/modules/checkboxRowSelector.js
new file mode 100644
index 0000000000..e1d6c75ec9
--- /dev/null
+++ b/public/src/admin/modules/checkboxRowSelector.js
@@ -0,0 +1,49 @@
+'use strict';
+
+define('admin/modules/checkboxRowSelector', function () {
+ const self = {};
+ let $tableContainer;
+
+ self.toggling = false;
+
+ self.init = function (tableCssSelector) {
+ $tableContainer = $(tableCssSelector);
+ $tableContainer.on('change', 'input.checkbox-helper', handleChange);
+ };
+
+ self.updateAll = function () {
+ $tableContainer.find('input.checkbox-helper').each((idx, el) => {
+ self.updateState($(el));
+ });
+ };
+
+ self.updateState = function ($checkboxEl) {
+ if (self.toggling) {
+ return;
+ }
+ const checkboxes = $checkboxEl.closest('tr').find('input:not([disabled])').toArray();
+ const $toggler = $(checkboxes.shift());
+ const rowState = checkboxes.every(el => el.checked);
+ $toggler.prop('checked', rowState);
+ };
+
+ function handleChange(ev) {
+ const $checkboxEl = $(ev.target);
+ toggleAll($checkboxEl);
+ }
+
+ function toggleAll($checkboxEl) {
+ self.toggling = true;
+ const state = $checkboxEl.prop('checked');
+ $checkboxEl.closest('tr').find('input:not(.checkbox-helper)').each((idx, el) => {
+ const $checkbox = $(el);
+ if ($checkbox.prop('checked') === state) {
+ return;
+ }
+ $checkbox.click();
+ });
+ self.toggling = false;
+ }
+
+ return self;
+});
diff --git a/public/src/admin/settings/email.js b/public/src/admin/settings/email.js
index 3e3fdc1280..458163f8d7 100644
--- a/public/src/admin/settings/email.js
+++ b/public/src/admin/settings/email.js
@@ -82,7 +82,7 @@ define('admin/settings/email', ['ace/ace', 'admin/settings'], function (ace) {
hour = 0;
}
- socket.emit('meta.getServerTime', {}, function (err, now) {
+ socket.emit('admin.getServerTime', {}, function (err, now) {
if (err) {
return app.alertError(err.message);
}
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index ff23e50527..76b1a545ba 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -160,7 +160,11 @@ ajaxify = window.ajaxify || {};
window.location.href = data.responseJSON.external;
} else if (typeof data.responseJSON === 'string') {
ajaxifyTimer = undefined;
- ajaxify.go(data.responseJSON.slice(1), callback, quiet);
+ if (data.responseJSON.startsWith('http://') || data.responseJSON.startsWith('https://')) {
+ window.location.href = data.responseJSON;
+ } else {
+ ajaxify.go(data.responseJSON.slice(1), callback, quiet);
+ }
}
}
} else if (textStatus !== 'abort') {
diff --git a/public/src/app.js b/public/src/app.js
index 14a3593917..4bec1e1e69 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -60,6 +60,7 @@ app.cacheBuster = null;
earlyQueue.forEach(function (el) {
el.click();
});
+ earlyQueue = [];
});
} else {
setTimeout(app.handleEarlyClicks, 50);
@@ -113,16 +114,16 @@ app.cacheBuster = null;
if (app.user.uid > 0) {
unread.initUnreadTopics();
}
-
+ function finishLoad() {
+ $(window).trigger('action:app.load');
+ app.showMessages();
+ appLoaded = true;
+ }
overrides.overrideTimeago();
if (app.user.timeagoCode && app.user.timeagoCode !== 'en') {
- require(['timeago/locales/jquery.timeago.' + app.user.timeagoCode], function () {
- $(window).trigger('action:app.load');
- appLoaded = true;
- });
+ require(['timeago/locales/jquery.timeago.' + app.user.timeagoCode], finishLoad);
} else {
- $(window).trigger('action:app.load');
- appLoaded = true;
+ finishLoad();
}
});
};
@@ -193,7 +194,7 @@ app.cacheBuster = null;
};
app.handleInvalidSession = function () {
- if (app.flags._logout) {
+ if (app.flags._login || app.flags._logout) {
return;
}
diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js
index 7fc1ecd166..84a0e87a38 100644
--- a/public/src/client/account/edit.js
+++ b/public/src/client/account/edit.js
@@ -48,7 +48,7 @@ define('forum/account/edit', [
$(window).trigger('action:profile.update', userData);
api.put('/users/' + userData.uid, userData).then((res) => {
- app.alertSuccess('[[user:profile-update-success]]');
+ app.alertSuccess('[[user:profile_update_success]]');
if (res.picture) {
$('#user-current-picture').attr('src', res.picture);
diff --git a/public/src/client/account/edit/username.js b/public/src/client/account/edit/username.js
index 923b9ab13f..350ce38bd3 100644
--- a/public/src/client/account/edit/username.js
+++ b/public/src/client/account/edit/username.js
@@ -26,7 +26,7 @@ define('forum/account/edit/username', [
var btn = $(this);
btn.addClass('disabled').find('i').removeClass('hide');
- api.put('/users/' + userData.uid, userData).then((res) => {
+ api.put('/users/' + userData.uid, userData).then((response) => {
btn.removeClass('disabled').find('i').addClass('hide');
var userslug = slugify(userData.username);
if (userData.username && userslug && parseInt(userData.uid, 10) === parseInt(app.user.uid, 10)) {
@@ -34,7 +34,7 @@ define('forum/account/edit/username', [
$('[component="header/profilelink/edit"]').attr('href', config.relative_path + '/user/' + userslug + '/edit');
$('[component="header/profilelink/settings"]').attr('href', config.relative_path + '/user/' + userslug + '/settings');
$('[component="header/username"]').text(userData.username);
- $('[component="header/usericon"]').css('background-color', res.response['icon:bgColor']).text(res.response['icon:text']);
+ $('[component="header/usericon"]').css('background-color', response['icon:bgColor']).text(response['icon:text']);
}
ajaxify.go('user/' + userslug + '/edit');
diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js
index 74e7111f7f..e9ae03431c 100644
--- a/public/src/client/category/tools.js
+++ b/public/src/client/category/tools.js
@@ -6,7 +6,9 @@ define('forum/category/tools', [
'topicSelect',
'components',
'translator',
-], function (topicSelect, components, translator) {
+ 'api',
+ 'bootbox',
+], function (topicSelect, components, translator, api, bootbox) {
var CategoryTools = {};
CategoryTools.init = function () {
@@ -15,56 +17,41 @@ define('forum/category/tools', [
handlePinnedTopicSort();
components.get('topic/delete').on('click', function () {
- categoryCommand('delete', topicSelect.getSelectedTids());
+ categoryCommand('del', '/state', 'delete', onDeletePurgeComplete);
return false;
});
components.get('topic/restore').on('click', function () {
- categoryCommand('restore', topicSelect.getSelectedTids());
+ categoryCommand('put', '/state', 'restore', onDeletePurgeComplete);
return false;
});
components.get('topic/purge').on('click', function () {
- categoryCommand('purge', topicSelect.getSelectedTids());
+ categoryCommand('del', '', 'purge', onDeletePurgeComplete);
return false;
});
components.get('topic/lock').on('click', function () {
- var tids = topicSelect.getSelectedTids();
- if (!tids.length) {
- return app.alertError('[[error:no-topics-selected]]');
- }
- socket.emit('topics.lock', { tids: tids }, onCommandComplete);
+ categoryCommand('put', '/lock', 'lock', onCommandComplete);
return false;
});
components.get('topic/unlock').on('click', function () {
- var tids = topicSelect.getSelectedTids();
- if (!tids.length) {
- return app.alertError('[[error:no-topics-selected]]');
- }
- socket.emit('topics.unlock', { tids: tids }, onCommandComplete);
+ categoryCommand('del', '/lock', 'unlock', onCommandComplete);
return false;
});
components.get('topic/pin').on('click', function () {
- var tids = topicSelect.getSelectedTids();
- if (!tids.length) {
- return app.alertError('[[error:no-topics-selected]]');
- }
- socket.emit('topics.pin', { tids: tids }, onCommandComplete);
+ categoryCommand('put', '/pin', 'pin', onCommandComplete);
return false;
});
components.get('topic/unpin').on('click', function () {
- var tids = topicSelect.getSelectedTids();
- if (!tids.length) {
- return app.alertError('[[error:no-topics-selected]]');
- }
- socket.emit('topics.unpin', { tids: tids }, onCommandComplete);
+ categoryCommand('del', '/pin', 'unpin', onCommandComplete);
return false;
});
+ // todo: should also use categoryCommand, but no write api call exists for this yet
components.get('topic/mark-unread-for-all').on('click', function () {
var tids = topicSelect.getSelectedTids();
if (!tids.length) {
@@ -136,18 +123,73 @@ define('forum/category/tools', [
socket.on('event:topic_moved', onTopicMoved);
};
- function categoryCommand(command, tids) {
+ function categoryCommand(method, path, command, onComplete) {
+ if (!onComplete) {
+ onComplete = function () {};
+ }
+ const tids = topicSelect.getSelectedTids();
+ const body = {};
+ const execute = function (ok) {
+ if (ok) {
+ Promise.all(tids.map(tid => api[method](`/topics/${tid}${path}`, body)))
+ .then(onComplete)
+ .catch(app.alertError);
+ }
+ };
+
if (!tids.length) {
return app.alertError('[[error:no-topics-selected]]');
}
- translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function (msg) {
- bootbox.confirm(msg, function (confirm) {
- if (!confirm) {
- return;
- }
+ switch (command) {
+ case 'delete':
+ case 'restore':
+ case 'purge':
+ bootbox.confirm(`[[topic:thread_tools.${command}_confirm]]`, execute);
+ break;
- socket.emit('topics.' + command, { tids: tids }, onDeletePurgeComplete);
+ case 'pin':
+ requestPinExpiry(body, execute.bind(null, true));
+ break;
+
+ default:
+ execute(true);
+ break;
+ }
+ }
+
+ function requestPinExpiry(body, onSuccess) {
+ app.parseAndTranslate('modals/set-pin-expiry', {}, function (html) {
+ const modal = bootbox.dialog({
+ title: '[[topic:thread_tools.pin]]',
+ message: html,
+ onEscape: true,
+ size: 'small',
+ buttons: {
+ save: {
+ label: '[[global:save]]',
+ className: 'btn-primary',
+ callback: function () {
+ const expiryEl = modal.get(0).querySelector('#expiry');
+ let expiry = expiryEl.value;
+
+ // No expiry set
+ if (expiry === '') {
+ return onSuccess();
+ }
+
+ // Expiration date set
+ expiry = new Date(expiry);
+
+ if (expiry && expiry.getTime() > Date.now()) {
+ body.expiry = expiry.getTime();
+ onSuccess();
+ } else {
+ app.alertError('[[error:invalid-date]]');
+ }
+ },
+ },
+ },
});
});
}
@@ -167,18 +209,12 @@ define('forum/category/tools', [
$('.thread-tools.open').find('.dropdown-toggle').trigger('click');
}
- function onCommandComplete(err) {
- if (err) {
- return app.alertError(err.message);
- }
+ function onCommandComplete() {
closeDropDown();
topicSelect.unselectAll();
}
- function onDeletePurgeComplete(err) {
- if (err) {
- return app.alertError(err.message);
- }
+ function onDeletePurgeComplete() {
closeDropDown();
updateDropdownOptions();
}
diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js
index 90df269834..fba32d00e6 100644
--- a/public/src/client/groups/details.js
+++ b/public/src/client/groups/details.js
@@ -143,6 +143,7 @@ define('forum/groups/details', [
var textColorValueEl = settingsFormEl.find('[name="textColor"]');
var iconBtn = settingsFormEl.find('[data-action="icon-select"]');
var previewEl = settingsFormEl.find('.label');
+ var previewElText = settingsFormEl.find('.label-text');
var previewIcon = previewEl.find('i');
var userTitleEl = settingsFormEl.find('[name="userTitle"]');
var userTitleEnabledEl = settingsFormEl.find('[name="userTitleEnabled"]');
@@ -165,9 +166,7 @@ define('forum/groups/details', [
// If the user title changes, update that too
userTitleEl.on('keyup', function () {
- var icon = previewIcon.detach();
- previewEl.text(' ' + (this.value || settingsFormEl.find('#name').val()));
- previewEl.prepend(icon);
+ previewElText.translateText((this.value || settingsFormEl.find('#name').val()));
});
// Disable user title customisation options if the the user title itself is disabled
diff --git a/public/src/client/login.js b/public/src/client/login.js
index 20ff9c8eba..91a69c3166 100644
--- a/public/src/client/login.js
+++ b/public/src/client/login.js
@@ -29,6 +29,9 @@ define('forum/login', ['jquery-form'], function () {
headers: {
'x-csrf-token': config.csrf_token,
},
+ beforeSend: function () {
+ app.flags._login = true;
+ },
success: function (data) {
$(window).trigger('action:app.loggedIn', data);
var pathname = utils.urlToLocation(data.next).pathname;
diff --git a/public/src/client/register.js b/public/src/client/register.js
index 26096c29e1..8010835c86 100644
--- a/public/src/client/register.js
+++ b/public/src/client/register.js
@@ -151,12 +151,12 @@ define('forum/register', [
callback = callback || function () {};
var username_notify = $('#username-notify');
-
- if (username.length < ajaxify.data.minimumUsernameLength) {
+ var userslug = slugify(username);
+ if (username.length < ajaxify.data.minimumUsernameLength || userslug.length < ajaxify.data.minimumUsernameLength) {
showError(username_notify, '[[error:username-too-short]]');
} else if (username.length > ajaxify.data.maximumUsernameLength) {
showError(username_notify, '[[error:username-too-long]]');
- } else if (!utils.isUserNameValid(username) || !slugify(username)) {
+ } else if (!utils.isUserNameValid(username) || !userslug) {
showError(username_notify, '[[error:invalid-username]]');
} else {
Promise.allSettled([
diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js
index 1f46cbbfc3..8e14941524 100644
--- a/public/src/client/topic/posts.js
+++ b/public/src/client/topic/posts.js
@@ -47,11 +47,16 @@ define('forum/topic/posts', [
Posts.modifyPostsByPrivileges = function (posts) {
posts.forEach(function (post) {
post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10);
+ post.topicOwnerPost = parseInt(post.uid, 10) === parseInt(ajaxify.data.uid, 10);
+
post.display_edit_tools = (ajaxify.data.privileges['posts:edit'] && post.selfPost) || ajaxify.data.privileges.isAdminOrMod;
post.display_delete_tools = (ajaxify.data.privileges['posts:delete'] && post.selfPost) || ajaxify.data.privileges.isAdminOrMod;
post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools;
post.display_move_tools = ajaxify.data.privileges.isAdminOrMod;
- post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || (post.selfPost && !ajaxify.data.locked) || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted);
+ post.display_post_menu = ajaxify.data.privileges.isAdminOrMod ||
+ (post.selfPost && !ajaxify.data.locked && !post.deleted) ||
+ (post.selfPost && post.deleted && parseInt(post.deleterUid, 10) === parseInt(app.user.uid, 10)) ||
+ ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted);
});
};
diff --git a/public/src/sockets.js b/public/src/sockets.js
index a5b087222c..4c8e1bc5f3 100644
--- a/public/src/sockets.js
+++ b/public/src/sockets.js
@@ -101,7 +101,6 @@ socket = window.socket;
function onConnect() {
if (!reconnecting) {
- app.showMessages();
$(window).trigger('action:connected');
}
diff --git a/src/analytics.js b/src/analytics.js
index b3f706e0aa..d1ec60af2d 100644
--- a/src/analytics.js
+++ b/src/analytics.js
@@ -41,7 +41,7 @@ Analytics.init = async function () {
Analytics.increment = function (keys, callback) {
keys = Array.isArray(keys) ? keys : [keys];
- plugins.fireHook('action:analytics.increment', { keys: keys });
+ plugins.hooks.fire('action:analytics.increment', { keys: keys });
keys.forEach(function (key) {
counters[key] = counters[key] || 0;
diff --git a/src/api/helpers.js b/src/api/helpers.js
index da4d76fddc..2e82cd2399 100644
--- a/src/api/helpers.js
+++ b/src/api/helpers.js
@@ -109,7 +109,7 @@ exports.postCommand = async function (caller, command, eventName, notification,
filter:post.bookmark
filter:post.unbookmark
*/
- const filteredData = await plugins.fireHook('filter:post.' + command, {
+ const filteredData = await plugins.hooks.fire('filter:post.' + command, {
data: data,
uid: caller.uid,
});
diff --git a/src/api/users.js b/src/api/users.js
index d83d740371..08d4bc6af1 100644
--- a/src/api/users.js
+++ b/src/api/users.js
@@ -82,15 +82,15 @@ usersAPI.update = async function (caller, data) {
};
usersAPI.delete = async function (caller, { uid, password }) {
- processDeletion({ uid: uid, method: 'delete', password, caller });
+ await processDeletion({ uid: uid, method: 'delete', password, caller });
};
usersAPI.deleteContent = async function (caller, { uid, password }) {
- processDeletion({ uid, method: 'deleteContent', password, caller });
+ await processDeletion({ uid, method: 'deleteContent', password, caller });
};
usersAPI.deleteAccount = async function (caller, { uid, password }) {
- processDeletion({ uid, method: 'deleteAccount', password, caller });
+ await processDeletion({ uid, method: 'deleteAccount', password, caller });
};
usersAPI.deleteMany = async function (caller, data) {
@@ -128,7 +128,7 @@ usersAPI.changePassword = async function (caller, data) {
usersAPI.follow = async function (caller, data) {
await user.follow(caller.uid, data.uid);
- plugins.fireHook('action:user.follow', {
+ plugins.hooks.fire('action:user.follow', {
fromUid: caller.uid,
toUid: data.uid,
});
@@ -151,7 +151,7 @@ usersAPI.follow = async function (caller, data) {
usersAPI.unfollow = async function (caller, data) {
await user.unfollow(caller.uid, data.uid);
- plugins.fireHook('action:user.unfollow', {
+ plugins.hooks.fire('action:user.unfollow', {
fromUid: caller.uid,
toUid: data.uid,
});
@@ -185,7 +185,7 @@ usersAPI.ban = async function (caller, data) {
ip: caller.ip,
reason: data.reason || undefined,
});
- plugins.fireHook('action:user.banned', {
+ plugins.hooks.fire('action:user.banned', {
callerUid: caller.uid,
ip: caller.ip,
uid: data.uid,
@@ -207,7 +207,7 @@ usersAPI.unban = async function (caller, data) {
targetUid: data.uid,
ip: caller.ip,
});
- plugins.fireHook('action:user.unbanned', {
+ plugins.hooks.fire('action:user.unbanned', {
callerUid: caller.uid,
ip: caller.ip,
uid: data.uid,
@@ -265,7 +265,6 @@ async function processDeletion({ uid, method, password, caller }) {
}
}
- // TODO: clear user tokens for this uid
await flags.resolveFlag('user', uid, caller.uid);
let userData;
@@ -278,7 +277,7 @@ async function processDeletion({ uid, method, password, caller }) {
sockets.server.sockets.emit('event:user_status_change', { uid: caller.uid, status: 'offline' });
- plugins.fireHook('action:user.delete', {
+ plugins.hooks.fire('action:user.delete', {
callerUid: caller.uid,
uid: uid,
ip: caller.ip,
diff --git a/src/categories/create.js b/src/categories/create.js
index 56f7a694f2..46aae0a097 100644
--- a/src/categories/create.js
+++ b/src/categories/create.js
@@ -45,7 +45,7 @@ module.exports = function (Categories) {
category.backgroundImage = data.backgroundImage;
}
- const result = await plugins.fireHook('filter:category.create', { category: category, data: data });
+ const result = await plugins.hooks.fire('filter:category.create', { category: category, data: data });
category = result.category;
@@ -86,7 +86,7 @@ module.exports = function (Categories) {
await duplicateCategoriesChildren(category.cid, data.cloneFromCid, data.uid);
}
- plugins.fireHook('action:category.create', { category: category });
+ plugins.hooks.fire('action:category.create', { category: category });
return category;
};
@@ -170,7 +170,7 @@ module.exports = function (Categories) {
Categories.copyPrivilegesFrom = async function (fromCid, toCid, group) {
group = group || '';
- const data = await plugins.fireHook('filter:categories.copyPrivilegesFrom', {
+ const data = await plugins.hooks.fire('filter:categories.copyPrivilegesFrom', {
privileges: group ? privileges.groupPrivilegeList.slice() : privileges.privilegeList.slice(),
fromCid: fromCid,
toCid: toCid,
diff --git a/src/categories/data.js b/src/categories/data.js
index 9097fb5aa0..4e3dbb1365 100644
--- a/src/categories/data.js
+++ b/src/categories/data.js
@@ -20,7 +20,7 @@ module.exports = function (Categories) {
const keys = cids.map(cid => 'category:' + cid);
const categories = await (fields.length ? db.getObjectsFields(keys, fields) : db.getObjects(keys));
- const result = await plugins.fireHook('filter:category.getFields', {
+ const result = await plugins.hooks.fire('filter:category.getFields', {
cids: cids,
categories: categories,
fields: fields,
diff --git a/src/categories/delete.js b/src/categories/delete.js
index 30c92ae441..74395f0658 100644
--- a/src/categories/delete.js
+++ b/src/categories/delete.js
@@ -22,7 +22,7 @@ module.exports = function (Categories) {
await topics.purgePostsAndTopic(tid, uid);
});
await purgeCategory(cid);
- plugins.fireHook('action:category.delete', { cid: cid, uid: uid });
+ plugins.hooks.fire('action:category.delete', { cid: cid, uid: uid });
};
async function purgeCategory(cid) {
diff --git a/src/categories/index.js b/src/categories/index.js
index f13f7a7a2d..5fb73e475f 100644
--- a/src/categories/index.js
+++ b/src/categories/index.js
@@ -59,7 +59,7 @@ Categories.getCategoryById = async function (data) {
calculateTopicPostCount(category);
- const result = await plugins.fireHook('filter:category.get', { category: category, uid: data.uid });
+ const result = await plugins.hooks.fire('filter:category.get', { category: category, uid: data.uid });
return result.category;
};
diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js
index 3a7e37d383..efb9381d90 100644
--- a/src/categories/recentreplies.js
+++ b/src/categories/recentreplies.js
@@ -36,7 +36,7 @@ module.exports = function (Categories) {
if (numRecentReplies > 0) {
await db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid);
}
- await plugins.fireHook('action:categories.updateRecentTid', { cid: cid, tid: tid });
+ await plugins.hooks.fire('action:categories.updateRecentTid', { cid: cid, tid: tid });
};
Categories.updateRecentTidForCid = async function (cid) {
@@ -68,8 +68,8 @@ module.exports = function (Categories) {
}
const categoriesToLoad = categoryData.filter(c => c && c.numRecentReplies && parseInt(c.numRecentReplies, 10) > 0);
let keys = [];
- if (plugins.hasListeners('filter:categories.getRecentTopicReplies')) {
- const result = await plugins.fireHook('filter:categories.getRecentTopicReplies', {
+ if (plugins.hooks.hasListeners('filter:categories.getRecentTopicReplies')) {
+ const result = await plugins.hooks.fire('filter:categories.getRecentTopicReplies', {
categories: categoriesToLoad,
uid: uid,
query: query,
diff --git a/src/categories/topics.js b/src/categories/topics.js
index bf396b38bb..1326960754 100644
--- a/src/categories/topics.js
+++ b/src/categories/topics.js
@@ -8,7 +8,7 @@ const user = require('../user');
module.exports = function (Categories) {
Categories.getCategoryTopics = async function (data) {
- let results = await plugins.fireHook('filter:category.topics.prepare', data);
+ let results = await plugins.hooks.fire('filter:category.topics.prepare', data);
const tids = await Categories.getTopicIds(results);
let topicsData = await topics.getTopicsByTids(tids, data.uid);
topicsData = await user.blocks.filter(data.uid, topicsData);
@@ -18,7 +18,7 @@ module.exports = function (Categories) {
}
topics.calculateTopicIndices(topicsData, data.start);
- results = await plugins.fireHook('filter:category.topics.get', { cid: data.cid, topics: topicsData, uid: data.uid });
+ results = await plugins.hooks.fire('filter:category.topics.get', { cid: data.cid, topics: topicsData, uid: data.uid });
return { topics: results.topics, nextStart: data.stop + 1 };
};
@@ -43,8 +43,8 @@ module.exports = function (Categories) {
return pinnedTidsOnPage;
}
- if (plugins.hasListeners('filter:categories.getTopicIds')) {
- const result = await plugins.fireHook('filter:categories.getTopicIds', {
+ if (plugins.hooks.hasListeners('filter:categories.getTopicIds')) {
+ const result = await plugins.hooks.fire('filter:categories.getTopicIds', {
tids: [],
data: data,
pinnedTids: pinnedTidsOnPage,
@@ -74,8 +74,8 @@ module.exports = function (Categories) {
};
Categories.getTopicCount = async function (data) {
- if (plugins.hasListeners('filter:categories.getTopicCount')) {
- const result = await plugins.fireHook('filter:categories.getTopicCount', {
+ if (plugins.hooks.hasListeners('filter:categories.getTopicCount')) {
+ const result = await plugins.hooks.fire('filter:categories.getTopicCount', {
topicCount: data.category.topic_count,
data: data,
});
@@ -112,7 +112,7 @@ module.exports = function (Categories) {
set = [set, 'tag:' + data.tag + ':topics'];
}
}
- const result = await plugins.fireHook('filter:categories.buildTopicsSortedSet', {
+ const result = await plugins.hooks.fire('filter:categories.buildTopicsSortedSet', {
set: set,
data: data,
});
@@ -122,7 +122,7 @@ module.exports = function (Categories) {
Categories.getSortedSetRangeDirection = async function (sort) {
sort = sort || 'newest_to_oldest';
const direction = sort === 'newest_to_oldest' || sort === 'most_posts' || sort === 'most_votes' ? 'highest-to-lowest' : 'lowest-to-highest';
- const result = await plugins.fireHook('filter:categories.getSortedSetRangeDirection', {
+ const result = await plugins.hooks.fire('filter:categories.getSortedSetRangeDirection', {
sort: sort,
direction: direction,
});
@@ -134,15 +134,15 @@ module.exports = function (Categories) {
};
Categories.getPinnedTids = async function (data) {
- if (plugins.hasListeners('filter:categories.getPinnedTids')) {
- const result = await plugins.fireHook('filter:categories.getPinnedTids', {
+ if (plugins.hooks.hasListeners('filter:categories.getPinnedTids')) {
+ const result = await plugins.hooks.fire('filter:categories.getPinnedTids', {
pinnedTids: [],
data: data,
});
return result && result.pinnedTids;
}
-
- return await db.getSortedSetRevRange('cid:' + data.cid + ':tids:pinned', data.start, data.stop);
+ const pinnedTids = await db.getSortedSetRevRange('cid:' + data.cid + ':tids:pinned', data.start, data.stop);
+ return await topics.tools.checkPinExpiry(pinnedTids);
};
Categories.modifyTopicsByPrivilege = function (topics, privileges) {
diff --git a/src/categories/update.js b/src/categories/update.js
index 3086b1bf8c..51ca422380 100644
--- a/src/categories/update.js
+++ b/src/categories/update.js
@@ -27,7 +27,7 @@ module.exports = function (Categories) {
const translated = await translator.translate(modifiedFields.name);
modifiedFields.slug = cid + '/' + slugify(translated);
}
- const result = await plugins.fireHook('filter:category.update', { cid: cid, category: modifiedFields });
+ const result = await plugins.hooks.fire('filter:category.update', { cid: cid, category: modifiedFields });
const category = result.category;
var fields = Object.keys(category);
@@ -40,7 +40,7 @@ module.exports = function (Categories) {
await async.eachSeries(fields, async function (key) {
await updateCategoryField(cid, key, category[key]);
});
- plugins.fireHook('action:category.update', { cid: cid, modified: category });
+ plugins.hooks.fire('action:category.update', { cid: cid, modified: category });
}
async function updateCategoryField(cid, key, value) {
@@ -92,7 +92,7 @@ module.exports = function (Categories) {
}
Categories.parseDescription = async function (cid, description) {
- const parsedDescription = await plugins.fireHook('filter:parse.raw', description);
+ const parsedDescription = await plugins.hooks.fire('filter:parse.raw', description);
await Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription);
};
};
diff --git a/src/controllers/404.js b/src/controllers/404.js
index eb9f94c8d8..529d8f339b 100644
--- a/src/controllers/404.js
+++ b/src/controllers/404.js
@@ -12,8 +12,8 @@ exports.handle404 = function handle404(req, res) {
const relativePath = nconf.get('relative_path');
const isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js(\\?v=\\w+)?$');
- if (plugins.hasListeners('action:meta.override404')) {
- return plugins.fireHook('action:meta.override404', {
+ if (plugins.hooks.hasListeners('action:meta.override404')) {
+ return plugins.hooks.fire('action:meta.override404', {
req: req,
res: res,
error: {},
diff --git a/src/controllers/accounts/blocks.js b/src/controllers/accounts/blocks.js
index 2dec4776fe..fac52e8227 100644
--- a/src/controllers/accounts/blocks.js
+++ b/src/controllers/accounts/blocks.js
@@ -19,7 +19,7 @@ blocksController.getBlocks = async function (req, res, next) {
return next();
}
const uids = await user.blocks.list(userData.uid);
- const data = await plugins.fireHook('filter:user.getBlocks', {
+ const data = await plugins.hooks.fire('filter:user.getBlocks', {
uids: uids,
uid: userData.uid,
start: start,
@@ -30,7 +30,7 @@ blocksController.getBlocks = async function (req, res, next) {
userData.users = await user.getUsers(data.uids, req.uid);
userData.title = '[[pages:account/blocks, ' + userData.username + ']]';
- const pageCount = Math.ceil(userData.blocksCount / resultsPerPage);
+ const pageCount = Math.ceil(userData.counts.blocks / resultsPerPage);
userData.pagination = pagination.create(page, pageCount);
userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: '/user/' + userData.userslug }, { text: '[[user:blocks]]' }]);
diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js
index b56a725bc8..ed07c2a6f9 100644
--- a/src/controllers/accounts/helpers.js
+++ b/src/controllers/accounts/helpers.js
@@ -3,6 +3,7 @@
const validator = require('validator');
const nconf = require('nconf');
+const db = require('../../database');
const user = require('../../user');
const groups = require('../../groups');
const plugins = require('../../plugins');
@@ -11,6 +12,7 @@ const utils = require('../../utils');
const privileges = require('../../privileges');
const translator = require('../../translator');
const messaging = require('../../messaging');
+const categories = require('../../categories');
const helpers = module.exports;
@@ -57,10 +59,6 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID) {
}
userData.isBlocked = results.isBlocked;
- if (isAdmin || isSelf) {
- userData.blocksCount = await user.getUserField(userData.uid, 'blocksCount');
- }
-
userData.yourid = callerUID;
userData.theirid = userData.uid;
userData.isTargetAdmin = results.isTargetAdmin;
@@ -112,7 +110,10 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID) {
userData['cover:position'] = validator.escape(String(userData['cover:position'] || '50% 50%'));
userData['username:disableEdit'] = !userData.isAdmin && meta.config['username:disableEdit'];
userData['email:disableEdit'] = !userData.isAdmin && meta.config['email:disableEdit'];
- const hookData = await plugins.fireHook('filter:helpers.getUserDataByUserSlug', { userData: userData, callerUID: callerUID });
+
+ await getCounts(userData, callerUID);
+
+ const hookData = await plugins.hooks.fire('filter:helpers.getUserDataByUserSlug', { userData: userData, callerUID: callerUID });
return hookData.userData;
};
@@ -128,7 +129,7 @@ async function getAllData(uid, callerUID) {
ips: user.getIPs(uid, 4),
profile_menu: getProfileMenu(uid, callerUID),
groups: groups.getUserGroups([uid]),
- sso: plugins.fireHook('filter:auth.list', { uid: uid, associations: [] }),
+ sso: plugins.hooks.fire('filter:auth.list', { uid: uid, associations: [] }),
canEdit: privileges.users.canEdit(callerUID, uid),
canBanUser: privileges.users.canBanUser(callerUID, uid),
isBlocked: user.blocks.is(uid, callerUID),
@@ -137,6 +138,33 @@ async function getAllData(uid, callerUID) {
});
}
+async function getCounts(userData, callerUID) {
+ const uid = userData.uid;
+ const cids = await categories.getCidsByPrivilege('categories:cid', callerUID, 'topics:read');
+ const promises = {
+ posts: db.sortedSetsCardSum(cids.map(c => 'cid:' + c + ':uid:' + uid + ':pids')),
+ best: db.sortedSetsCardSum(cids.map(c => 'cid:' + c + ':uid:' + uid + ':pids:votes')),
+ topics: db.sortedSetsCardSum(cids.map(c => 'cid:' + c + ':uid:' + uid + ':tids')),
+ };
+ if (userData.isAdmin || userData.isSelf) {
+ promises.ignored = db.sortedSetCard('uid:' + uid + ':ignored_tids');
+ promises.watched = db.sortedSetCard('uid:' + uid + ':followed_tids');
+ promises.upvoted = db.sortedSetCard('uid:' + uid + ':upvote');
+ promises.downvoted = db.sortedSetCard('uid:' + uid + ':downvote');
+ promises.bookmarks = db.sortedSetCard('uid:' + uid + ':bookmarks');
+ promises.uploaded = db.sortedSetCard('uid:' + uid + ':uploads');
+ promises.categoriesWatched = user.getWatchedCategories(uid);
+ promises.blocks = user.getUserField(userData.uid, 'blocksCount');
+ }
+ const counts = await utils.promiseParallel(promises);
+ counts.categoriesWatched = counts.categoriesWatched && counts.categoriesWatched.length;
+ counts.groups = userData.groups.length;
+ counts.following = userData.followingCount;
+ counts.followers = userData.followerCount;
+ userData.blocksCount = counts.blocks || 0; // for backwards compatibility, remove in 1.16.0
+ userData.counts = counts;
+}
+
async function getProfileMenu(uid, callerUID) {
const links = [{
id: 'info',
@@ -183,7 +211,7 @@ async function getProfileMenu(uid, callerUID) {
});
}
- return await plugins.fireHook('filter:user.profileMenu', {
+ return await plugins.hooks.fire('filter:user.profileMenu', {
uid: uid,
callerUID: callerUID,
links: links,
@@ -197,7 +225,7 @@ async function parseAboutMe(userData) {
return;
}
userData.aboutme = validator.escape(String(userData.aboutme || ''));
- const parsed = await plugins.fireHook('filter:parse.aboutme', userData.aboutme);
+ const parsed = await plugins.hooks.fire('filter:parse.aboutme', userData.aboutme);
userData.aboutmeParsed = translator.escape(parsed);
}
diff --git a/src/controllers/accounts/notifications.js b/src/controllers/accounts/notifications.js
index 9ef4ece4cf..2e1bb83833 100644
--- a/src/controllers/accounts/notifications.js
+++ b/src/controllers/accounts/notifications.js
@@ -30,7 +30,7 @@ notificationsController.get = async function (req, res, next) {
const stop = start + itemsPerPage - 1;
const [filters, isPrivileged] = await Promise.all([
- plugins.fireHook('filter:notifications.addFilters', {
+ plugins.hooks.fire('filter:notifications.addFilters', {
regularFilters: regularFilters,
moderatorFilters: moderatorFilters,
uid: req.uid,
diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js
index f52d43a483..b80bb4b46a 100644
--- a/src/controllers/accounts/settings.js
+++ b/src/controllers/accounts/settings.js
@@ -33,7 +33,7 @@ settingsController.get = async function (req, res, next) {
userData.acpLanguages = _.cloneDeep(languagesData);
}
- const data = await plugins.fireHook('filter:user.customSettings', {
+ const data = await plugins.hooks.fire('filter:user.customSettings', {
settings: settings,
customSettings: [],
uid: req.uid,
@@ -110,7 +110,7 @@ settingsController.get = async function (req, res, next) {
userData.hideFullname = meta.config.hideFullname || 0;
userData.hideEmail = meta.config.hideEmail || 0;
- userData.inTopicSearchAvailable = plugins.hasListeners('filter:topic.search');
+ userData.inTopicSearchAvailable = plugins.hooks.hasListeners('filter:topic.search');
userData.maxTopicsPerPage = meta.config.maxTopicsPerPage;
userData.maxPostsPerPage = meta.config.maxPostsPerPage;
@@ -191,7 +191,7 @@ async function getNotificationSettings(userData) {
if (privileges.isAdmin || privileges.isGlobalMod) {
privilegedTypes.push('notificationType_new-user-flag');
}
- const results = await plugins.fireHook('filter:user.notificationTypes', {
+ const results = await plugins.hooks.fire('filter:user.notificationTypes', {
types: notifications.baseTypes.slice(),
privilegedTypes: privilegedTypes,
});
diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js
index 49f987da63..796b36de25 100644
--- a/src/controllers/admin/categories.js
+++ b/src/controllers/admin/categories.js
@@ -27,7 +27,7 @@ categoriesController.get = async function (req, res, next) {
});
const selectedCategory = allCategories.find(c => c.selected);
- const data = await plugins.fireHook('filter:admin.category.get', {
+ const data = await plugins.hooks.fire('filter:admin.category.get', {
req: req,
res: res,
category: category,
@@ -53,7 +53,7 @@ categoriesController.getAll = async function (req, res) {
'color', 'bgColor', 'backgroundImage', 'imageClass',
];
const categoriesData = await categories.getCategoriesFields(cids, fields);
- const result = await plugins.fireHook('filter:admin.categories.get', { categories: categoriesData, fields: fields });
+ const result = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields });
const tree = categories.getTree(result.categories, 0);
res.render('admin/manage/categories', {
categories: tree,
diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js
index 89d8c1b222..cef8c8921a 100644
--- a/src/controllers/admin/dashboard.js
+++ b/src/controllers/admin/dashboard.js
@@ -47,7 +47,7 @@ async function getNotices() {
notDoneText: '[[admin/dashboard:restart-required]]',
},
{
- done: plugins.hasListeners('filter:search.query'),
+ done: plugins.hooks.hasListeners('filter:search.query'),
doneText: '[[admin/dashboard:search-plugin-installed]]',
notDoneText: '[[admin/dashboard:search-plugin-not-installed]]',
tooltip: '[[admin/dashboard:search-plugin-tooltip]]',
@@ -62,7 +62,7 @@ async function getNotices() {
});
}
- return await plugins.fireHook('filter:admin.notices', notices);
+ return await plugins.hooks.fire('filter:admin.notices', notices);
}
async function getLatestVersion() {
diff --git a/src/controllers/admin/groups.js b/src/controllers/admin/groups.js
index c099d2093e..a4c2e6a228 100644
--- a/src/controllers/admin/groups.js
+++ b/src/controllers/admin/groups.js
@@ -35,9 +35,10 @@ groupsController.list = async function (req, res) {
groupsController.get = async function (req, res, next) {
const groupName = req.params.name;
- const [groupNames, group] = await Promise.all([
+ const [groupNames, group, allCategories] = await Promise.all([
getGroupNames(),
groups.get(groupName, { uid: req.uid, truncateUserList: true, userListCount: 20 }),
+ categories.buildForSelectAll(),
]);
if (!group) {
@@ -53,8 +54,6 @@ groupsController.get = async function (req, res, next) {
};
});
- const allCategories = await categories.buildForSelectAll();
-
res.render('admin/manage/group', {
group: group,
groupNames: groupNameData,
diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js
index 36dec3a0ef..9919deb8cc 100644
--- a/src/controllers/admin/info.js
+++ b/src/controllers/admin/info.js
@@ -93,6 +93,7 @@ async function getNodeInfo() {
data.process.cpuUsage.system /= 1000000;
data.process.cpuUsage.system = data.process.cpuUsage.system.toFixed(2);
data.process.memoryUsage.humanReadable = (data.process.memoryUsage.rss / (1024 * 1024)).toFixed(2);
+ data.process.uptimeHumanReadable = humanReadableUptime(data.process.uptime);
data.os.freemem = (data.os.freemem / 1000000).toFixed(2);
data.os.totalmem = (data.os.totalmem / 1000000).toFixed(2);
const [stats, gitInfo] = await Promise.all([
@@ -104,6 +105,17 @@ async function getNodeInfo() {
return data;
}
+function humanReadableUptime(seconds) {
+ if (seconds < 60) {
+ return Math.floor(seconds) + 's';
+ } else if (seconds < 3600) {
+ return Math.floor(seconds / 60) + 'm';
+ } else if (seconds < 3600 * 24) {
+ return Math.floor(seconds / (60 * 60)) + 'h';
+ }
+ return Math.floor(seconds / (60 * 60 * 24)) + 'd';
+}
+
async function getGitInfo() {
function get(cmd, callback) {
exec(cmd, function (err, stdout) {
diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js
index 15f02e664c..01b3d5da6e 100644
--- a/src/controllers/admin/uploads.js
+++ b/src/controllers/admin/uploads.js
@@ -237,8 +237,8 @@ function validateUpload(res, uploadedFile, allowedTypes) {
async function uploadImage(filename, folder, uploadedFile, req, res, next) {
let imageData;
try {
- if (plugins.hasListeners('filter:uploadImage')) {
- imageData = await plugins.fireHook('filter:uploadImage', { image: uploadedFile, uid: req.uid, folder: folder });
+ if (plugins.hooks.hasListeners('filter:uploadImage')) {
+ imageData = await plugins.hooks.fire('filter:uploadImage', { image: uploadedFile, uid: req.uid, folder: folder });
} else {
imageData = await file.saveFileToLocal(filename, folder, uploadedFile.path);
}
diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js
index 99f3d4231b..ccb7fa1e42 100644
--- a/src/controllers/admin/users.js
+++ b/src/controllers/admin/users.js
@@ -189,7 +189,7 @@ usersController.registrationQueue = async function (req, res) {
const data = await utils.promiseParallel({
registrationQueueCount: db.sortedSetCard('registration:queue'),
users: user.getRegistrationQueue(start, stop),
- customHeaders: plugins.fireHook('filter:admin.registrationQueue.customHeaders', { headers: [] }),
+ customHeaders: plugins.hooks.fire('filter:admin.registrationQueue.customHeaders', { headers: [] }),
invites: getInvites(),
});
var pageCount = Math.max(1, Math.ceil(data.registrationQueueCount / itemsPerPage));
diff --git a/src/controllers/api.js b/src/controllers/api.js
index 6407b01d32..c3d36999ff 100644
--- a/src/controllers/api.js
+++ b/src/controllers/api.js
@@ -65,7 +65,7 @@ apiController.loadConfig = async function (req) {
topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
categoryTopicSort: meta.config.categoryTopicSort || 'newest_to_oldest',
csrf_token: req.uid >= 0 && req.csrfToken && req.csrfToken(),
- searchEnabled: plugins.hasListeners('filter:search.query'),
+ searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
bootswatchSkin: meta.config.bootswatchSkin || '',
enablePostHistory: meta.config.enablePostHistory === 1,
timeagoCutoff: meta.config.timeagoCutoff !== '' ? Math.max(0, parseInt(meta.config.timeagoCutoff, 10)) : meta.config.timeagoCutoff,
@@ -98,7 +98,7 @@ apiController.loadConfig = async function (req) {
config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
config.topicSearchEnabled = settings.topicSearchEnabled || false;
config.bootswatchSkin = (meta.config.disableCustomUserSkins !== 1 && settings.bootswatchSkin && settings.bootswatchSkin !== '') ? settings.bootswatchSkin : '';
- config = await plugins.fireHook('filter:config.get', config);
+ config = await plugins.hooks.fire('filter:config.get', config);
return config;
};
diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js
index 7d52e76082..eec386bb8f 100644
--- a/src/controllers/authentication.js
+++ b/src/controllers/authentication.js
@@ -22,7 +22,7 @@ const sockets = require('../socket.io');
const authenticationController = module.exports;
async function registerAndLoginUser(req, res, userData) {
- const data = await plugins.fireHook('filter:register.interstitial', {
+ const data = await plugins.hooks.fire('filter:register.interstitial', {
userData: userData,
interstitials: [],
});
@@ -45,7 +45,7 @@ async function registerAndLoginUser(req, res, userData) {
return;
}
const queue = await user.shouldQueueUser(req.ip);
- const result = await plugins.fireHook('filter:register.shouldQueue', { req: req, res: res, userData: userData, queue: queue });
+ const result = await plugins.hooks.fire('filter:register.shouldQueue', { req: req, res: res, userData: userData, queue: queue });
if (result.queue) {
return await addToApprovalQueue(req, userData);
}
@@ -61,7 +61,7 @@ async function registerAndLoginUser(req, res, userData) {
}
await user.deleteInvitationKey(userData.email);
const referrer = req.body.referrer || req.session.referrer || nconf.get('relative_path') + '/';
- const complete = await plugins.fireHook('filter:register.complete', { uid: uid, referrer: referrer });
+ const complete = await plugins.hooks.fire('filter:register.complete', { uid: uid, referrer: referrer });
req.session.returnTo = complete.referrer;
return complete;
}
@@ -105,7 +105,7 @@ authenticationController.register = async function (req, res) {
user.isPasswordValid(userData.password);
res.locals.processLogin = true; // set it to false in plugin if you wish to just register only
- await plugins.fireHook('filter:register.check', { req: req, res: res, userData: userData });
+ await plugins.hooks.fire('filter:register.check', { req: req, res: res, userData: userData });
const data = await registerAndLoginUser(req, res, userData);
if (data) {
@@ -137,7 +137,7 @@ async function addToApprovalQueue(req, userData) {
authenticationController.registerComplete = function (req, res, next) {
// For the interstitials that respond, execute the callback with the form body
- plugins.fireHook('filter:register.interstitial', {
+ plugins.hooks.fire('filter:register.interstitial', {
userData: req.session.registration,
interstitials: [],
}, function (err, data) {
@@ -217,14 +217,14 @@ authenticationController.registerAbort = function (req, res) {
};
authenticationController.login = function (req, res, next) {
- if (plugins.hasListeners('action:auth.overrideLogin')) {
+ if (plugins.hooks.hasListeners('action:auth.overrideLogin')) {
return continueLogin(req, res, next);
}
var loginWith = meta.config.allowLoginWith || 'username-email';
req.body.username = req.body.username.trim();
- plugins.fireHook('filter:login.check', { req: req, res: res, userData: req.body }, (err) => {
+ plugins.hooks.fire('filter:login.check', { req: req, res: res, userData: req.body }, (err) => {
if (err) {
return helpers.noScriptErrors(req, res, err.message, 403);
}
@@ -328,6 +328,7 @@ authenticationController.onSuccessfulLogin = async function (req, uid) {
req.loggedIn = true;
await meta.blacklist.test(req.ip);
await user.logIP(uid, req.ip);
+ await user.bans.unbanIfExpired([uid]);
req.session.meta = {};
@@ -355,7 +356,7 @@ authenticationController.onSuccessfulLogin = async function (req, uid) {
// Force session check for all connected socket.io clients with the same session id
sockets.in('sess_' + req.sessionID).emit('checkSession', uid);
- plugins.fireHook('action:user.loggedIn', { uid: uid, req: req });
+ plugins.hooks.fire('action:user.loggedIn', { uid: uid, req: req });
} catch (err) {
req.session.destroy();
throw err;
@@ -428,14 +429,14 @@ authenticationController.logout = async function (req, res, next) {
await user.setUserField(uid, 'lastonline', Date.now() - (meta.config.onlineCutoff * 60000));
await db.sortedSetAdd('users:online', Date.now() - (meta.config.onlineCutoff * 60000), uid);
- await plugins.fireHook('static:user.loggedOut', { req: req, res: res, uid: uid, sessionID: sessionID });
+ await plugins.hooks.fire('static:user.loggedOut', { req: req, res: res, uid: uid, sessionID: sessionID });
// Force session check for all connected socket.io clients with the same session id
sockets.in('sess_' + sessionID).emit('checkSession', 0);
const payload = {
next: nconf.get('relative_path') + '/',
};
- plugins.fireHook('filter:user.logout', payload);
+ plugins.hooks.fire('filter:user.logout', payload);
if (req.body.noscript === 'true') {
return res.redirect(payload.next);
diff --git a/src/controllers/category.js b/src/controllers/category.js
index 1815f67f1a..53984d04a6 100644
--- a/src/controllers/category.js
+++ b/src/controllers/category.js
@@ -2,6 +2,7 @@
const nconf = require('nconf');
+const validator = require('validator');
const db = require('../database');
const privileges = require('../privileges');
@@ -29,7 +30,7 @@ categoryController.get = async function (req, res, next) {
}
const [categoryFields, userPrivileges, userSettings, rssToken] = await Promise.all([
- categories.getCategoryFields(cid, ['slug', 'disabled']),
+ categories.getCategoryFields(cid, ['slug', 'disabled', 'link']),
privileges.categories.get(cid, req.uid),
user.getSettings(req.uid),
user.auth.getFeedToken(req.uid),
@@ -52,6 +53,10 @@ categoryController.get = async function (req, res, next) {
return helpers.redirect(res, '/category/' + categoryFields.slug, true);
}
+ if (categoryFields.link) {
+ await db.incrObjectField('category:' + cid, 'timesClicked');
+ return helpers.redirect(res, validator.unescape(categoryFields.link));
+ }
if (!userSettings.usePagination) {
topicIndex = Math.max(0, topicIndex - (Math.ceil(userSettings.topicsPerPage / 2) - 1));
@@ -89,10 +94,7 @@ categoryController.get = async function (req, res, next) {
}
categories.modifyTopicsByPrivilege(categoryData.topics, userPrivileges);
- if (categoryData.link) {
- await db.incrObjectField('category:' + categoryData.cid, 'timesClicked');
- return helpers.redirect(res, categoryData.link);
- }
+
await buildBreadcrumbs(req, categoryData);
if (categoryData.children.length) {
const allCategories = [];
diff --git a/src/controllers/composer.js b/src/controllers/composer.js
index 3a30e3dab0..ca0dc208f6 100644
--- a/src/controllers/composer.js
+++ b/src/controllers/composer.js
@@ -15,7 +15,7 @@ exports.get = async function (req, res, callback) {
content: 'noindex',
};
- const data = await plugins.fireHook('filter:composer.build', {
+ const data = await plugins.hooks.fire('filter:composer.build', {
req: req,
res: res,
next: callback,
diff --git a/src/controllers/errors.js b/src/controllers/errors.js
index 2052952067..bb8951b038 100644
--- a/src/controllers/errors.js
+++ b/src/controllers/errors.js
@@ -65,7 +65,7 @@ exports.handleErrors = function handleErrors(err, req, res, next) { // eslint-di
}
};
- plugins.fireHook('filter:error.handle', {
+ plugins.hooks.fire('filter:error.handle', {
cases: cases,
}, function (_err, data) {
if (_err) {
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index 8bbe2ffe1a..62ee51106c 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -1,5 +1,6 @@
'use strict';
+const colors = require('colors/safe');
const nconf = require('nconf');
const validator = require('validator');
const querystring = require('querystring');
@@ -116,7 +117,7 @@ helpers.buildTerms = function (url, term, query) {
};
helpers.notAllowed = async function (req, res, error) {
- const data = await plugins.fireHook('filter:helpers.notAllowed', {
+ const data = await plugins.hooks.fire('filter:helpers.notAllowed', {
req: req,
res: res,
error: error,
@@ -145,9 +146,11 @@ helpers.notAllowed = async function (req, res, error) {
helpers.redirect = function (res, url, permanent) {
if (res.locals.isAPI) {
- res.set('X-Redirect', encodeURI(url)).status(200).json(url);
+ res.set('X-Redirect', encodeURI(url)).status(200).json(encodeURI(url));
} else {
- res.redirect(permanent ? 308 : 307, relative_path + encodeURI(url));
+ const redirectUrl = url.startsWith('http://') || url.startsWith('https://') ?
+ url : relative_path + url;
+ res.redirect(permanent ? 308 : 307, encodeURI(redirectUrl));
}
};
@@ -336,7 +339,7 @@ helpers.getHomePageRoutes = async function (uid) {
name: 'Custom',
},
]);
- const data = await plugins.fireHook('filter:homepage.get', { routes: routes });
+ const data = await plugins.hooks.fire('filter:homepage.get', { routes: routes });
return data.routes;
};
@@ -355,9 +358,14 @@ helpers.formatApiResponse = async (statusCode, res, payload) => {
});
} else if (payload instanceof Error) {
const message = payload.message;
+ const response = {};
// Update status code based on some common error codes
switch (payload.message) {
+ case '[[error:user-banned]]':
+ Object.assign(response, await generateBannedResponse(res));
+ // intentional fall through
+
case '[[error:no-privileges]]':
statusCode = 403;
break;
@@ -368,9 +376,11 @@ helpers.formatApiResponse = async (statusCode, res, payload) => {
}
const returnPayload = helpers.generateError(statusCode, message);
+ returnPayload.response = response;
if (global.env === 'development') {
returnPayload.stack = payload.stack;
+ process.stdout.write(`[${colors.yellow('api')}] Exception caught, error with stack trace follows:\n`);
process.stdout.write(payload.stack);
}
res.status(statusCode).json(returnPayload);
@@ -380,6 +390,25 @@ helpers.formatApiResponse = async (statusCode, res, payload) => {
}
};
+async function generateBannedResponse(res) {
+ const response = {};
+ const [reason, expiry] = await Promise.all([
+ user.bans.getReason(res.req.uid),
+ user.getUserField(res.req.uid, 'banned:expire'),
+ ]);
+
+ response.reason = reason;
+ if (expiry) {
+ Object.assign(response, {
+ expiry,
+ expiryISO: new Date(expiry).toISOString(),
+ expiryLocaleString: new Date(expiry).toLocaleString(),
+ });
+ }
+
+ return response;
+}
+
helpers.generateError = (statusCode, message) => {
var payload = {
status: {
diff --git a/src/controllers/home.js b/src/controllers/home.js
index e882f57e21..4b69e719c7 100644
--- a/src/controllers/home.js
+++ b/src/controllers/home.js
@@ -39,7 +39,7 @@ async function rewrite(req, res, next) {
const pathname = parsedUrl.pathname;
const hook = 'action:homepage.get:' + pathname;
- if (!plugins.hasListeners(hook)) {
+ if (!plugins.hooks.hasListeners(hook)) {
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + pathname;
} else {
res.locals.homePageRoute = pathname;
@@ -54,7 +54,7 @@ exports.rewrite = rewrite;
function pluginHook(req, res, next) {
var hook = 'action:homepage.get:' + res.locals.homePageRoute;
- plugins.fireHook(hook, {
+ plugins.hooks.fire(hook, {
req: req,
res: res,
next: next,
diff --git a/src/controllers/index.js b/src/controllers/index.js
index 51e866a051..18f9ea3820 100644
--- a/src/controllers/index.js
+++ b/src/controllers/index.js
@@ -184,7 +184,7 @@ Controllers.registerInterstitial = async function (req, res, next) {
return res.redirect(nconf.get('relative_path') + '/register');
}
try {
- const data = await plugins.fireHook('filter:register.interstitial', {
+ const data = await plugins.hooks.fire('filter:register.interstitial', {
userData: req.session.registration,
interstitials: [],
});
@@ -298,7 +298,7 @@ Controllers.manifest = async function (req, res) {
});
}
- const data = await plugins.fireHook('filter:manifest.build', {
+ const data = await plugins.hooks.fire('filter:manifest.build', {
req: req,
res: res,
manifest: manifest,
@@ -331,7 +331,7 @@ Controllers.termsOfUse = async function (req, res, next) {
if (!meta.config.termsOfUse) {
return next();
}
- const termsOfUse = await plugins.fireHook('filter:parse.post', {
+ const termsOfUse = await plugins.hooks.fire('filter:parse.post', {
postData: {
content: meta.config.termsOfUse || '',
},
diff --git a/src/controllers/mods.js b/src/controllers/mods.js
index 859d24406c..b043ce98e7 100644
--- a/src/controllers/mods.js
+++ b/src/controllers/mods.js
@@ -30,8 +30,8 @@ modsController.flags.list = async function (req, res, next) {
const results = await Promise.all([
user.isAdminOrGlobalMod(req.uid),
user.getModeratedCids(req.uid),
- plugins.fireHook('filter:flags.validateFilters', { filters: validFilters }),
- plugins.fireHook('filter:flags.validateSort', { sorts: validSorts }),
+ plugins.hooks.fire('filter:flags.validateFilters', { filters: validFilters }),
+ plugins.hooks.fire('filter:flags.validateSort', { sorts: validSorts }),
]);
const [isAdminOrGlobalMod, moderatedCids,, { sorts }] = results;
let [,, { filters }] = results;
@@ -226,7 +226,7 @@ modsController.postQueue = async function (req, res, next) {
(!categoriesData.selectedCids.length || categoriesData.selectedCids.includes(p.category.cid)) &&
(isAdminOrGlobalMod || moderatedCids.includes(String(p.category.cid))));
- ({ posts: postData } = await plugins.fireHook('filter:post-queue.get', {
+ ({ posts: postData } = await plugins.hooks.fire('filter:post-queue.get', {
posts: postData,
req: req,
}));
@@ -281,6 +281,6 @@ async function addMetaData(postData) {
postData.topic = await topics.getTopicFields(postData.data.tid, ['title', 'cid']);
}
postData.category = await categories.getCategoryData(postData.topic.cid);
- const result = await plugins.fireHook('filter:parse.post', { postData: postData.data });
+ const result = await plugins.hooks.fire('filter:parse.post', { postData: postData.data });
postData.data.content = result.postData.content;
}
diff --git a/src/controllers/osd.js b/src/controllers/osd.js
index 58fa13a8e1..95142b0111 100644
--- a/src/controllers/osd.js
+++ b/src/controllers/osd.js
@@ -7,7 +7,7 @@ const plugins = require('../plugins');
const meta = require('../meta');
module.exports.handle = function (req, res, next) {
- if (plugins.hasListeners('filter:search.query')) {
+ if (plugins.hooks.hasListeners('filter:search.query')) {
res.type('application/opensearchdescription+xml').send(generateXML());
} else {
next();
diff --git a/src/controllers/popular.js b/src/controllers/popular.js
index 371eb5f42d..29f62c80ab 100644
--- a/src/controllers/popular.js
+++ b/src/controllers/popular.js
@@ -22,7 +22,7 @@ popularController.get = async function (req, res, next) {
}
const feedQs = data.rssFeedUrl.split('?')[1];
- data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (validator.escape(String(req.query.term)) || 'alltime') + '.rss';
+ data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + validator.escape(String(req.query.term || 'alltime')) + '.rss';
if (req.loggedIn) {
data.rssFeedUrl += '?' + feedQs;
}
diff --git a/src/controllers/search.js b/src/controllers/search.js
index 178fe2dc6f..c93e33dba3 100644
--- a/src/controllers/search.js
+++ b/src/controllers/search.js
@@ -15,7 +15,7 @@ const helpers = require('./helpers');
const searchController = module.exports;
searchController.search = async function (req, res, next) {
- if (!plugins.hasListeners('filter:search.query')) {
+ if (!plugins.hooks.hasListeners('filter:search.query')) {
return next();
}
const page = Math.max(1, parseInt(req.query.page, 10)) || 1;
diff --git a/src/controllers/top.js b/src/controllers/top.js
index 4a482baaf5..40fd8b869c 100644
--- a/src/controllers/top.js
+++ b/src/controllers/top.js
@@ -20,7 +20,7 @@ topController.get = async function (req, res, next) {
}
const feedQs = data.rssFeedUrl.split('?')[1];
- data.rssFeedUrl = nconf.get('relative_path') + '/top/' + (validator.escape(String(req.query.term)) || 'alltime') + '.rss';
+ data.rssFeedUrl = nconf.get('relative_path') + '/top/' + validator.escape(String(req.query.term || 'alltime')) + '.rss';
if (req.loggedIn) {
data.rssFeedUrl += '?' + feedQs;
}
diff --git a/src/controllers/unread.js b/src/controllers/unread.js
index ef9c804836..ff155b72a8 100644
--- a/src/controllers/unread.js
+++ b/src/controllers/unread.js
@@ -64,8 +64,8 @@ unreadController.get = async function (req, res) {
};
async function getWatchedCategories(uid, cid, filter) {
- if (plugins.hasListeners('filter:unread.categories')) {
- return await plugins.fireHook('filter:unread.categories', { uid: uid, cid: cid });
+ if (plugins.hooks.hasListeners('filter:unread.categories')) {
+ return await plugins.hooks.fire('filter:unread.categories', { uid: uid, cid: cid });
}
const states = [categories.watchStates.watching];
if (filter === 'watched') {
diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js
index a916ba62db..df1f930401 100644
--- a/src/controllers/uploads.js
+++ b/src/controllers/uploads.js
@@ -56,8 +56,8 @@ async function uploadAsImage(req, uploadedFile) {
await image.checkDimensions(uploadedFile.path);
await image.stripEXIF(uploadedFile.path);
- if (plugins.hasListeners('filter:uploadImage')) {
- return await plugins.fireHook('filter:uploadImage', {
+ if (plugins.hooks.hasListeners('filter:uploadImage')) {
+ return await plugins.hooks.fire('filter:uploadImage', {
image: uploadedFile,
uid: req.uid,
folder: 'files',
@@ -117,13 +117,14 @@ uploadsController.uploadThumb = async function (req, res, next) {
throw new Error('[[error:invalid-file]]');
}
await image.isFileTypeAllowed(uploadedFile.path);
+ await image.checkDimensions(uploadedFile.path);
await image.resizeImage({
path: uploadedFile.path,
width: meta.config.topicThumbSize,
height: meta.config.topicThumbSize,
});
- if (plugins.hasListeners('filter:uploadImage')) {
- return await plugins.fireHook('filter:uploadImage', {
+ if (plugins.hooks.hasListeners('filter:uploadImage')) {
+ return await plugins.hooks.fire('filter:uploadImage', {
image: uploadedFile,
uid: req.uid,
folder: 'files',
@@ -135,8 +136,8 @@ uploadsController.uploadThumb = async function (req, res, next) {
};
uploadsController.uploadFile = async function (uid, uploadedFile) {
- if (plugins.hasListeners('filter:uploadFile')) {
- return await plugins.fireHook('filter:uploadFile', {
+ if (plugins.hooks.hasListeners('filter:uploadFile')) {
+ return await plugins.hooks.fire('filter:uploadFile', {
file: uploadedFile,
uid: uid,
folder: 'files',
@@ -175,7 +176,7 @@ async function saveFileToLocal(uid, folder, uploadedFile) {
};
const fileKey = upload.url.replace(nconf.get('upload_url'), '');
await db.sortedSetAdd('uid:' + uid + ':uploads', Date.now(), fileKey);
- const data = await plugins.fireHook('filter:uploadStored', { uid: uid, uploadedFile: uploadedFile, storedFile: storedFile });
+ const data = await plugins.hooks.fire('filter:uploadStored', { uid: uid, uploadedFile: uploadedFile, storedFile: storedFile });
return data.storedFile;
}
diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js
index 7e44b4184f..81b06d01a0 100644
--- a/src/controllers/write/topics.js
+++ b/src/controllers/write/topics.js
@@ -38,6 +38,12 @@ Topics.purge = async (req, res) => {
Topics.pin = async (req, res) => {
await api.topics.pin(req, { tids: [req.params.tid] });
+
+ // Pin expiry was not available w/ sockets hence not included in api lib method
+ if (req.body.expiry) {
+ topics.tools.setPinExpiry(req.params.tid, req.body.expiry, req.uid);
+ }
+
helpers.formatApiResponse(200, res);
};
diff --git a/src/emailer.js b/src/emailer.js
index 9a5d67e07d..83e16f5d97 100644
--- a/src/emailer.js
+++ b/src/emailer.js
@@ -264,7 +264,7 @@ Emailer.sendToEmail = async (template, email, language, params) => {
params.unsubUrl = unsubUrl;
}
- const result = await Plugins.fireHook('filter:email.params', {
+ const result = await Plugins.hooks.fire('filter:email.params', {
template: template,
email: email,
language: lang,
@@ -280,7 +280,7 @@ Emailer.sendToEmail = async (template, email, language, params) => {
translator.translate(params.subject, result.language),
]);
- const data = await Plugins.fireHook('filter:email.modify', {
+ const data = await Plugins.hooks.fire('filter:email.modify', {
_raw: params,
to: email,
from: meta.config['email:from'] || 'no-reply@' + getHostname(),
@@ -299,8 +299,8 @@ Emailer.sendToEmail = async (template, email, language, params) => {
});
try {
- if (Plugins.hasListeners('filter:email.send')) {
- await Plugins.fireHook('filter:email.send', data);
+ if (Plugins.hooks.hasListeners('filter:email.send')) {
+ await Plugins.hooks.fire('filter:email.send', data);
} else {
await Emailer.sendViaFallback(data);
}
diff --git a/src/events.js b/src/events.js
index 7630dfcfa7..46a67b27d9 100644
--- a/src/events.js
+++ b/src/events.js
@@ -79,7 +79,7 @@ events.log = async function (data) {
], data.timestamp, eid),
db.setObject('event:' + eid, data),
]);
- plugins.fireHook('action:events.log', { data: data });
+ plugins.hooks.fire('action:events.log', { data: data });
};
events.getEvents = async function (filter, start, stop, from, to) {
diff --git a/src/flags.js b/src/flags.js
index 98ecfb2251..8c716765b2 100644
--- a/src/flags.js
+++ b/src/flags.js
@@ -76,7 +76,7 @@ Flags.init = async function () {
};
try {
- const data = await plugins.fireHook('filter:flags.getFilters', hookData);
+ const data = await plugins.hooks.fire('filter:flags.getFilters', hookData);
Flags._filters = data.filters;
} catch (err) {
winston.error('[flags/init] Could not retrieve filters', err.stack);
@@ -107,7 +107,7 @@ Flags.get = async function (flagId) {
reports: reports,
};
- const data = await plugins.fireHook('filter:flags.get', {
+ const data = await plugins.hooks.fire('filter:flags.get', {
flag: flagObj,
});
return data.flag;
@@ -177,7 +177,7 @@ Flags.list = async function (data) {
});
}));
- const payload = await plugins.fireHook('filter:flags.list', {
+ const payload = await plugins.hooks.fire('filter:flags.list', {
flags: flags,
page: filters.page,
uid: data.uid,
@@ -587,7 +587,7 @@ Flags.update = async function (flagId, uid, changeset) {
tasks.push(Flags.appendHistory(flagId, uid, changeset));
await Promise.all(tasks);
- plugins.fireHook('action:flags.update', { flagId: flagId, changeset: changeset, uid: uid });
+ plugins.hooks.fire('action:flags.update', { flagId: flagId, changeset: changeset, uid: uid });
};
Flags.resolveFlag = async function (type, id, uid) {
@@ -712,7 +712,7 @@ Flags.notify = async function (flagObj, uid) {
throw new Error('[[error:invalid-data]]');
}
- plugins.fireHook('action:flags.create', {
+ plugins.hooks.fire('action:flags.create', {
flag: flagObj,
});
uids = uids.filter(_uid => parseInt(_uid, 10) !== parseInt(uid, 10));
diff --git a/src/groups/create.js b/src/groups/create.js
index 637f919e6a..9bd56ebc1b 100644
--- a/src/groups/create.js
+++ b/src/groups/create.js
@@ -40,7 +40,7 @@ module.exports = function (Groups) {
disableLeave: disableLeave,
};
- await plugins.fireHook('filter:group.create', { group: groupData, data: data });
+ await plugins.hooks.fire('filter:group.create', { group: groupData, data: data });
await db.sortedSetAdd('groups:createtime', groupData.createtime, groupData.name);
await db.setObject('group:' + groupData.name, groupData);
@@ -61,7 +61,7 @@ module.exports = function (Groups) {
await db.setObjectField('groupslug:groupname', groupData.slug, groupData.name);
groupData = await Groups.getGroupData(groupData.name);
- plugins.fireHook('action:group.create', { group: groupData });
+ plugins.hooks.fire('action:group.create', { group: groupData });
return groupData;
};
diff --git a/src/groups/data.js b/src/groups/data.js
index 6747fc0078..ae518c27ca 100644
--- a/src/groups/data.js
+++ b/src/groups/data.js
@@ -6,6 +6,7 @@ const nconf = require('nconf');
const db = require('../database');
const plugins = require('../plugins');
const utils = require('../utils');
+const translator = require('../translator');
const intFields = [
'createtime', 'memberCount', 'hidden', 'system', 'private',
@@ -35,7 +36,7 @@ module.exports = function (Groups) {
groupData.forEach(group => modifyGroup(group, fields));
- const results = await plugins.fireHook('filter:groups.get', { groups: groupData });
+ const results = await plugins.hooks.fire('filter:groups.get', { groups: groupData });
return results.groups;
};
@@ -60,7 +61,7 @@ module.exports = function (Groups) {
Groups.setGroupField = async function (groupName, field, value) {
await db.setObjectField('group:' + groupName, field, value);
- plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' });
+ plugins.hooks.fire('action:group.set', { field: field, value: value, type: 'set' });
};
};
@@ -101,5 +102,6 @@ function escapeGroupData(group) {
group.displayName = validator.escape(String(group.name));
group.description = validator.escape(String(group.description || ''));
group.userTitle = validator.escape(String(group.userTitle || ''));
+ group.userTitleEscaped = translator.escape(group.userTitle);
}
}
diff --git a/src/groups/delete.js b/src/groups/delete.js
index eb6ef3b64e..24a5cc8694 100644
--- a/src/groups/delete.js
+++ b/src/groups/delete.js
@@ -41,7 +41,7 @@ module.exports = function (Groups) {
removeGroupsFromPrivilegeGroups(groupNames),
]);
Groups.cache.reset();
- plugins.fireHook('action:groups.destroy', { groups: groupsData });
+ plugins.hooks.fire('action:groups.destroy', { groups: groupsData });
};
async function removeGroupsFromPrivilegeGroups(groupNames) {
diff --git a/src/groups/index.js b/src/groups/index.js
index 732f415e6b..9cb83e7ab4 100644
--- a/src/groups/index.js
+++ b/src/groups/index.js
@@ -135,7 +135,7 @@ Groups.get = async function (groupName, options) {
if (!groupData) {
return null;
}
- const descriptionParsed = await plugins.fireHook('filter:parse.raw', groupData.description);
+ const descriptionParsed = await plugins.hooks.fire('filter:parse.raw', groupData.description);
groupData.descriptionParsed = descriptionParsed;
groupData.categories = selectCategories.map((category) => {
category.selected = groupData.memberPostCids.includes(category.cid);
@@ -149,7 +149,7 @@ Groups.get = async function (groupName, options) {
groupData.isPending = isPending;
groupData.isInvited = isInvited;
groupData.isOwner = isOwner;
- const results = await plugins.fireHook('filter:group.get', { group: groupData });
+ const results = await plugins.hooks.fire('filter:group.get', { group: groupData });
return results.group;
};
@@ -194,7 +194,7 @@ Groups.getOwnersAndMembers = async function (groupName, uid, start, stop) {
}
}
returnUsers = countToReturn > 0 ? returnUsers.slice(0, countToReturn) : returnUsers;
- const result = await plugins.fireHook('filter:group.getOwnersAndMembers', {
+ const result = await plugins.hooks.fire('filter:group.getOwnersAndMembers', {
users: returnUsers,
uid: uid,
start: start,
diff --git a/src/groups/invite.js b/src/groups/invite.js
index b96869d856..10d56a076b 100644
--- a/src/groups/invite.js
+++ b/src/groups/invite.js
@@ -83,7 +83,7 @@ module.exports = function (Groups) {
const set = type === 'invite' ? 'group:' + groupName + ':invited' : 'group:' + groupName + ':pending';
await db.setAdd(set, uids);
const hookName = type === 'invite' ? 'action:group.inviteMember' : 'action:group.requestMembership';
- plugins.fireHook(hookName, {
+ plugins.hooks.fire(hookName, {
groupName: groupName,
uids: uids,
});
diff --git a/src/groups/join.js b/src/groups/join.js
index 09d94954a4..b2e1edc153 100644
--- a/src/groups/join.js
+++ b/src/groups/join.js
@@ -63,7 +63,7 @@ module.exports = function (Groups) {
await setGroupTitleIfNotSet(groupsToJoin, uid);
- plugins.fireHook('action:group.join', {
+ plugins.hooks.fire('action:group.join', {
groupNames: groupsToJoin,
uid: uid,
});
diff --git a/src/groups/leave.js b/src/groups/leave.js
index b7ca0c9b7c..32d27bbeca 100644
--- a/src/groups/leave.js
+++ b/src/groups/leave.js
@@ -53,7 +53,7 @@ module.exports = function (Groups) {
await clearGroupTitleIfSet(groupsToLeave, uid);
- plugins.fireHook('action:group.leave', {
+ plugins.hooks.fire('action:group.leave', {
groupNames: groupsToLeave,
uid: uid,
});
diff --git a/src/groups/ownership.js b/src/groups/ownership.js
index b3002e7169..4296c307c0 100644
--- a/src/groups/ownership.js
+++ b/src/groups/ownership.js
@@ -24,7 +24,7 @@ module.exports = function (Groups) {
Groups.ownership.grant = async function (toUid, groupName) {
// Note: No ownership checking is done here on purpose!
await db.setAdd('group:' + groupName + ':owners', toUid);
- plugins.fireHook('action:group.grantOwnership', { uid: toUid, groupName: groupName });
+ plugins.hooks.fire('action:group.grantOwnership', { uid: toUid, groupName: groupName });
};
Groups.ownership.rescind = async function (toUid, groupName) {
@@ -35,6 +35,6 @@ module.exports = function (Groups) {
throw new Error('[[error:group-needs-owner]]');
}
await db.setRemove('group:' + groupName + ':owners', toUid);
- plugins.fireHook('action:group.rescindOwnership', { uid: toUid, groupName: groupName });
+ plugins.hooks.fire('action:group.rescindOwnership', { uid: toUid, groupName: groupName });
};
};
diff --git a/src/groups/update.js b/src/groups/update.js
index 50027fc9bd..26d0cdad5c 100644
--- a/src/groups/update.js
+++ b/src/groups/update.js
@@ -19,7 +19,7 @@ module.exports = function (Groups) {
throw new Error('[[error:no-group]]');
}
- ({ values } = await plugins.fireHook('filter:group.update', {
+ ({ values } = await plugins.hooks.fire('filter:group.update', {
groupName: groupName,
values: values,
}));
@@ -75,7 +75,7 @@ module.exports = function (Groups) {
await db.setObject('group:' + groupName, payload);
await Groups.renameGroup(groupName, values.name);
- plugins.fireHook('action:group.update', {
+ plugins.hooks.fire('action:group.update', {
name: groupName,
values: values,
});
@@ -199,7 +199,7 @@ module.exports = function (Groups) {
await renameGroupsMember(['groups:createtime', 'groups:visible:createtime', 'groups:visible:memberCount'], oldName, newName);
await renameGroupsMember(['groups:visible:name'], oldName.toLowerCase() + ':' + oldName, newName.toLowerCase() + ':' + newName);
- plugins.fireHook('action:group.rename', {
+ plugins.hooks.fire('action:group.rename', {
old: oldName,
new: newName,
});
diff --git a/src/image.js b/src/image.js
index a1afc49677..a6b5310adb 100644
--- a/src/image.js
+++ b/src/image.js
@@ -23,8 +23,8 @@ function requireSharp() {
image.isFileTypeAllowed = async function (path) {
const plugins = require('./plugins');
- if (plugins.hasListeners('filter:image.isFileTypeAllowed')) {
- return await plugins.fireHook('filter:image.isFileTypeAllowed', path);
+ if (plugins.hooks.hasListeners('filter:image.isFileTypeAllowed')) {
+ return await plugins.hooks.fire('filter:image.isFileTypeAllowed', path);
}
const sharp = require('sharp');
await sharp(path, {
@@ -33,8 +33,8 @@ image.isFileTypeAllowed = async function (path) {
};
image.resizeImage = async function (data) {
- if (plugins.hasListeners('filter:image.resize')) {
- await plugins.fireHook('filter:image.resize', {
+ if (plugins.hooks.hasListeners('filter:image.resize')) {
+ await plugins.hooks.fire('filter:image.resize', {
path: data.path,
target: data.target,
width: data.width,
@@ -61,8 +61,8 @@ image.resizeImage = async function (data) {
};
image.normalise = async function (path) {
- if (plugins.hasListeners('filter:image.normalise')) {
- await plugins.fireHook('filter:image.normalise', {
+ if (plugins.hooks.hasListeners('filter:image.normalise')) {
+ await plugins.hooks.fire('filter:image.normalise', {
path: path,
});
} else {
@@ -74,8 +74,8 @@ image.normalise = async function (path) {
image.size = async function (path) {
let imageData;
- if (plugins.hasListeners('filter:image.size')) {
- imageData = await plugins.fireHook('filter:image.size', {
+ if (plugins.hooks.hasListeners('filter:image.size')) {
+ imageData = await plugins.hooks.fire('filter:image.size', {
path: path,
});
} else {
@@ -138,8 +138,8 @@ image.sizeFromBase64 = function (imageData) {
};
image.uploadImage = async function (filename, folder, imageData) {
- if (plugins.hasListeners('filter:uploadImage')) {
- return await plugins.fireHook('filter:uploadImage', {
+ if (plugins.hooks.hasListeners('filter:uploadImage')) {
+ return await plugins.hooks.fire('filter:uploadImage', {
image: imageData,
uid: imageData.uid,
folder: folder,
diff --git a/src/messaging/create.js b/src/messaging/create.js
index 2d6a10b8e8..9c5c453eb7 100644
--- a/src/messaging/create.js
+++ b/src/messaging/create.js
@@ -24,7 +24,7 @@ module.exports = function (Messaging) {
const maximumChatMessageLength = meta.config.maximumChatMessageLength || 1000;
content = String(content).trim();
let length = String(content.length).trim();
- ({ content, length } = await plugins.fireHook('filter:messaging.checkContent', { content, length }));
+ ({ content, length } = await plugins.hooks.fire('filter:messaging.checkContent', { content, length }));
if (!content) {
throw new Error('[[error:invalid-chat-message]]');
}
@@ -49,7 +49,7 @@ module.exports = function (Messaging) {
message.ip = data.ip;
}
- message = await plugins.fireHook('filter:messaging.save', message);
+ message = await plugins.hooks.fire('filter:messaging.save', message);
await db.setObject('message:' + mid, message);
const isNewSet = await Messaging.isNewSet(data.uid, data.roomId, timestamp);
let uids = await db.getSortedSetRange('chat:room:' + data.roomId + ':uids', 0, -1);
@@ -69,7 +69,7 @@ module.exports = function (Messaging) {
messages[0].newSet = isNewSet;
messages[0].mid = mid;
messages[0].roomId = data.roomId;
- plugins.fireHook('action:messaging.save', { message: messages[0], data: data });
+ plugins.hooks.fire('action:messaging.save', { message: messages[0], data: data });
return messages[0];
};
diff --git a/src/messaging/data.js b/src/messaging/data.js
index 7a99d648c3..2098986ad5 100644
--- a/src/messaging/data.js
+++ b/src/messaging/data.js
@@ -121,7 +121,7 @@ module.exports = function (Messaging) {
messages = [];
}
- const data = await plugins.fireHook('filter:messaging.getMessages', {
+ const data = await plugins.hooks.fire('filter:messaging.getMessages', {
messages: messages,
uid: uid,
roomId: roomId,
@@ -144,7 +144,7 @@ async function modifyMessage(message, fields, mid) {
}
}
- const payload = await plugins.fireHook('filter:messaging.getFields', {
+ const payload = await plugins.hooks.fire('filter:messaging.getFields', {
mid: mid,
message: message,
fields: fields,
diff --git a/src/messaging/edit.js b/src/messaging/edit.js
index ca1e5a80eb..3e94d55bbb 100644
--- a/src/messaging/edit.js
+++ b/src/messaging/edit.js
@@ -16,7 +16,7 @@ module.exports = function (Messaging) {
return;
}
- const payload = await plugins.fireHook('filter:messaging.edit', {
+ const payload = await plugins.hooks.fire('filter:messaging.edit', {
content: content,
edited: Date.now(),
});
diff --git a/src/messaging/index.js b/src/messaging/index.js
index 757884e9a8..f5202d236f 100644
--- a/src/messaging/index.js
+++ b/src/messaging/index.js
@@ -54,7 +54,7 @@ Messaging.getMessages = async (params) => {
};
async function canGet(hook, callerUid, uid) {
- const data = await plugins.fireHook(hook, {
+ const data = await plugins.hooks.fire(hook, {
callerUid: callerUid,
uid: uid,
canGet: parseInt(callerUid, 10) === parseInt(uid, 10),
@@ -64,7 +64,7 @@ async function canGet(hook, callerUid, uid) {
}
Messaging.parse = async (message, fromuid, uid, roomId, isNew) => {
- const parsed = await plugins.fireHook('filter:parse.raw', message);
+ const parsed = await plugins.hooks.fire('filter:parse.raw', message);
let messageData = {
message: message,
parsed: parsed,
@@ -75,7 +75,7 @@ Messaging.parse = async (message, fromuid, uid, roomId, isNew) => {
parsedMessage: parsed,
};
- messageData = await plugins.fireHook('filter:messaging.parse', messageData);
+ messageData = await plugins.hooks.fire('filter:messaging.parse', messageData);
return messageData ? messageData.parsedMessage : '';
};
@@ -129,7 +129,7 @@ Messaging.getRecentChats = async (callerUid, uid, start, stop) => {
results.roomData = results.roomData.filter(Boolean);
const ref = { rooms: results.roomData, nextStart: stop + 1 };
- return await plugins.fireHook('filter:messaging.getRecentChats', {
+ return await plugins.hooks.fire('filter:messaging.getRecentChats', {
rooms: ref.rooms,
nextStart: ref.nextStart,
uid: uid,
@@ -160,7 +160,7 @@ Messaging.getTeaser = async (uid, roomId) => {
teaser.content = validator.escape(String(teaser.content));
}
- const payload = await plugins.fireHook('filter:messaging.getTeaser', { teaser: teaser });
+ const payload = await plugins.hooks.fire('filter:messaging.getTeaser', { teaser: teaser });
return payload.teaser;
};
@@ -222,7 +222,7 @@ Messaging.canMessageUser = async (uid, toUid) => {
throw new Error('[[error:chat-restricted]]');
}
- await plugins.fireHook('static:messaging.canMessageUser', {
+ await plugins.hooks.fire('static:messaging.canMessageUser', {
uid: uid,
toUid: toUid,
});
@@ -247,7 +247,7 @@ Messaging.canMessageRoom = async (uid, roomId) => {
throw new Error('[[error:no-privileges]]');
}
- await plugins.fireHook('static:messaging.canMessageRoom', {
+ await plugins.hooks.fire('static:messaging.canMessageRoom', {
uid: uid,
roomId: roomId,
});
diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js
index 7571187190..63ccbc193e 100644
--- a/src/messaging/notifications.js
+++ b/src/messaging/notifications.js
@@ -19,7 +19,7 @@ module.exports = function (Messaging) {
message: messageObj,
uids: uids,
};
- data = await plugins.fireHook('filter:messaging.notify', data);
+ data = await plugins.hooks.fire('filter:messaging.notify', data);
if (!data || !data.uids || !data.uids.length) {
return;
}
diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js
index 87582b77d0..2d511199ca 100644
--- a/src/messaging/rooms.js
+++ b/src/messaging/rooms.js
@@ -61,7 +61,7 @@ module.exports = function (Messaging) {
Messaging.isUserInRoom = async (uid, roomId) => {
const inRoom = await db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
- const data = await plugins.fireHook('filter:messaging.isUserInRoom', { uid: uid, roomId: roomId, inRoom: inRoom });
+ const data = await plugins.hooks.fire('filter:messaging.isUserInRoom', { uid: uid, roomId: roomId, inRoom: inRoom });
return data.inRoom;
};
@@ -185,7 +185,7 @@ module.exports = function (Messaging) {
throw new Error('[[error:chat-room-name-too-long]]');
}
- const payload = await plugins.fireHook('filter:chat.renameRoom', {
+ const payload = await plugins.hooks.fire('filter:chat.renameRoom', {
uid: uid,
roomId: roomId,
newName: newName,
@@ -198,7 +198,7 @@ module.exports = function (Messaging) {
await db.setObjectField('chat:room:' + payload.roomId, 'roomName', payload.newName);
await Messaging.addSystemMessage('room-rename, ' + payload.newName.replace(',', ','), payload.uid, payload.roomId);
- plugins.fireHook('action:chat.renameRoom', {
+ plugins.hooks.fire('action:chat.renameRoom', {
roomId: payload.roomId,
newName: payload.newName,
});
@@ -206,7 +206,7 @@ module.exports = function (Messaging) {
Messaging.canReply = async (roomId, uid) => {
const inRoom = await db.isSortedSetMember('chat:room:' + roomId + ':uids', uid);
- const data = await plugins.fireHook('filter:messaging.canReply', { uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom });
+ const data = await plugins.hooks.fire('filter:messaging.canReply', { uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom });
return data.canReply;
};
diff --git a/src/meta/blacklist.js b/src/meta/blacklist.js
index 2c7ddf4678..7e2bd4d280 100644
--- a/src/meta/blacklist.js
+++ b/src/meta/blacklist.js
@@ -74,7 +74,7 @@ Blacklist.test = async function (clientIp) {
) {
try {
// To return test failure, pass back an error in callback
- await plugins.fireHook('filter:blacklist.test', { ip: clientIp });
+ await plugins.hooks.fire('filter:blacklist.test', { ip: clientIp });
} catch (err) {
analytics.increment('blacklist');
throw err;
diff --git a/src/meta/configs.js b/src/meta/configs.js
index a01654f7d3..ba00a1d712 100644
--- a/src/meta/configs.js
+++ b/src/meta/configs.js
@@ -150,7 +150,7 @@ Configs.remove = async function (field) {
};
Configs.registerHooks = () => {
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:settings.set',
method: async ({ plugin, settings, quiet }) => {
if (plugin === 'core.api' && Array.isArray(settings.tokens)) {
@@ -170,7 +170,7 @@ Configs.registerHooks = () => {
},
});
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:settings.get',
method: async ({ plugin, values }) => {
if (plugin === 'core.api' && Array.isArray(values.tokens)) {
diff --git a/src/meta/js.js b/src/meta/js.js
index 1859e0dd4a..b2b53367d6 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -245,22 +245,22 @@ async function requirejsOptimize(target) {
};
const bundledModules = [
{
- baseUrl: './node_modules',
+ baseUrl: path.join(basePath, 'node_modules'),
name: 'timeago/jquery.timeago',
},
{
- baseUrl: './node_modules/nprogress',
+ baseUrl: path.join(basePath, 'node_modules/nprogress'),
name: 'nprogress',
},
{
- baseUrl: './node_modules/bootbox',
+ baseUrl: path.join(basePath, 'node_modules/bootbox'),
name: 'bootbox',
},
];
const targetModules = {
admin: [
{
- baseUrl: './node_modules/sortablejs',
+ baseUrl: path.join(basePath, 'node_modules/sortablejs'),
name: 'Sortable',
},
],
diff --git a/src/meta/settings.js b/src/meta/settings.js
index eb8b87890a..5a290411da 100644
--- a/src/meta/settings.js
+++ b/src/meta/settings.js
@@ -31,7 +31,7 @@ Settings.get = async function (hash) {
});
}));
- ({ values: data } = await plugins.fireHook('filter:settings.get', { plugin: hash, values: data }));
+ ({ values: data } = await plugins.hooks.fire('filter:settings.get', { plugin: hash, values: data }));
cache.set('settings:' + hash, data);
return data;
};
@@ -54,7 +54,7 @@ Settings.set = async function (hash, values, quiet) {
}
}
- ({ plugin: hash, settings: values, quiet } = await plugins.fireHook('filter:settings.set', { plugin: hash, settings: values, quiet }));
+ ({ plugin: hash, settings: values, quiet } = await plugins.hooks.fire('filter:settings.set', { plugin: hash, settings: values, quiet }));
if (sortedLists.length) {
await db.delete('settings:' + hash + ':sorted-lists');
@@ -87,7 +87,7 @@ Settings.set = async function (hash, values, quiet) {
cache.del('settings:' + hash);
- plugins.fireHook('action:settings.set', {
+ plugins.hooks.fire('action:settings.set', {
plugin: hash,
settings: values,
});
diff --git a/src/meta/tags.js b/src/meta/tags.js
index c54c79554a..4282575e45 100644
--- a/src/meta/tags.js
+++ b/src/meta/tags.js
@@ -65,7 +65,7 @@ Tags.parse = async (req, data, meta, link) => {
href: relative_path + '/manifest.webmanifest',
}];
- if (plugins.hasListeners('filter:search.query')) {
+ if (plugins.hooks.hasListeners('filter:search.query')) {
defaultLinks.push({
rel: 'search',
type: 'application/opensearchdescription+xml',
@@ -140,8 +140,8 @@ Tags.parse = async (req, data, meta, link) => {
}
const results = await utils.promiseParallel({
- tags: plugins.fireHook('filter:meta.getMetaTags', { req: req, data: data, tags: defaultTags }),
- links: plugins.fireHook('filter:meta.getLinkTags', { req: req, data: data, links: defaultLinks }),
+ tags: plugins.hooks.fire('filter:meta.getMetaTags', { req: req, data: data, tags: defaultTags }),
+ links: plugins.hooks.fire('filter:meta.getLinkTags', { req: req, data: data, links: defaultLinks }),
});
meta = results.tags.tags.concat(meta || []).map(function (tag) {
diff --git a/src/middleware/admin.js b/src/middleware/admin.js
index 7f4a09acc7..765463281b 100644
--- a/src/middleware/admin.js
+++ b/src/middleware/admin.js
@@ -36,7 +36,7 @@ middleware.renderHeader = async (req, res, data) => {
const results = await utils.promiseParallel({
userData: user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed']),
scripts: getAdminScripts(),
- custom_header: plugins.fireHook('filter:admin.header.build', custom_header),
+ custom_header: plugins.hooks.fire('filter:admin.header.build', custom_header),
configs: meta.configs.list(),
latestVersion: getLatestVersion(),
privileges: privileges.admin.get(req.uid),
@@ -83,7 +83,7 @@ middleware.renderHeader = async (req, res, data) => {
};
async function getAdminScripts() {
- const scripts = await plugins.fireHook('filter:admin.scripts.get', []);
+ const scripts = await plugins.hooks.fire('filter:admin.scripts.get', []);
return scripts.map(function (script) {
return { src: script };
});
diff --git a/src/middleware/header.js b/src/middleware/header.js
index a5e041bc8e..2174f7c461 100644
--- a/src/middleware/header.js
+++ b/src/middleware/header.js
@@ -34,7 +34,7 @@ middleware.buildHeader = helpers.try(async function buildHeader(req, res, next)
const [config, isBanned] = await Promise.all([
controllers.api.loadConfig(req),
user.bans.isBanned(req.uid),
- plugins.fireHook('filter:middleware.buildHeader', { req: req, locals: res.locals }),
+ plugins.hooks.fire('filter:middleware.buildHeader', { req: req, locals: res.locals }),
]);
if (isBanned) {
@@ -60,7 +60,7 @@ middleware.renderHeader = async function renderHeader(req, res, data) {
'brand:logo:alt': meta.config['brand:logo:alt'] || '',
'brand:logo:display': meta.config['brand:logo'] ? '' : 'hide',
allowRegistration: registrationType === 'normal',
- searchEnabled: plugins.hasListeners('filter:search.query'),
+ searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
config: res.locals.config,
relative_path,
bodyClass: data.bodyClass,
@@ -173,7 +173,7 @@ middleware.renderHeader = async function renderHeader(req, res, data) {
modifyTitle(templateValues);
}
- const hookReturn = await plugins.fireHook('filter:middleware.renderHeader', {
+ const hookReturn = await plugins.hooks.fire('filter:middleware.renderHeader', {
req: req,
res: res,
templateValues: templateValues,
@@ -184,13 +184,13 @@ middleware.renderHeader = async function renderHeader(req, res, data) {
};
middleware.renderFooter = async function renderFooter(req, res, templateValues) {
- const data = await plugins.fireHook('filter:middleware.renderFooter', {
+ const data = await plugins.hooks.fire('filter:middleware.renderFooter', {
req: req,
res: res,
templateValues: templateValues,
});
- const scripts = await plugins.fireHook('filter:scripts.get', []);
+ const scripts = await plugins.hooks.fire('filter:scripts.get', []);
data.templateValues.scripts = scripts.map(function (script) {
return { src: script };
diff --git a/src/middleware/index.js b/src/middleware/index.js
index 1a9309cd1c..5e7e93e734 100644
--- a/src/middleware/index.js
+++ b/src/middleware/index.js
@@ -82,7 +82,7 @@ middleware.pageView = helpers.try(async function pageView(req, res, next) {
}
next();
await analytics.pageView({ ip: req.ip, uid: req.uid });
- plugins.fireHook('action:middleware.pageView', { req: req });
+ plugins.hooks.fire('action:middleware.pageView', { req: req });
});
middleware.pluginHooks = helpers.try(async function pluginHooks(req, res, next) {
@@ -91,7 +91,7 @@ middleware.pluginHooks = helpers.try(async function pluginHooks(req, res, next)
hookObj.method(req, res, next);
});
- await plugins.fireHook('response:router.page', {
+ await plugins.hooks.fire('response:router.page', {
req: req,
res: res,
});
@@ -227,7 +227,7 @@ middleware.trimUploadTimestamps = function trimUploadTimestamps(req, res, next)
middleware.validateAuth = helpers.try(async function validateAuth(req, res, next) {
try {
- await plugins.fireHook('static:auth.validate', {
+ await plugins.hooks.fire('static:auth.validate', {
user: res.locals.user,
strategy: res.locals.strategy,
});
diff --git a/src/middleware/render.js b/src/middleware/render.js
index 44a0689c0b..0462ca3671 100644
--- a/src/middleware/render.js
+++ b/src/middleware/render.js
@@ -33,10 +33,10 @@ module.exports = function (middleware) {
options.url = (req.baseUrl + req.path.replace(/^\/api/, ''));
options.bodyClass = buildBodyClass(req, res, options);
- const buildResult = await plugins.fireHook('filter:' + template + '.build', { req: req, res: res, templateData: options });
+ const buildResult = await plugins.hooks.fire('filter:' + template + '.build', { req: req, res: res, templateData: options });
const templateToRender = buildResult.templateData.templateToRender || template;
- const renderResult = await plugins.fireHook('filter:middleware.render', { req: req, res: res, templateData: buildResult.templateData });
+ const renderResult = await plugins.hooks.fire('filter:middleware.render', { req: req, res: res, templateData: buildResult.templateData });
options = renderResult.templateData;
options._header = {
tags: await meta.tags.parse(req, renderResult, res.locals.metaTags, res.locals.linkTags),
diff --git a/src/middleware/user.js b/src/middleware/user.js
index 63c3090160..903195ebd9 100644
--- a/src/middleware/user.js
+++ b/src/middleware/user.js
@@ -69,7 +69,7 @@ module.exports = function (middleware) {
}
}
- await plugins.fireHook('response:middleware.authenticate', {
+ await plugins.hooks.fire('response:middleware.authenticate', {
req: req,
res: res,
next: function () {}, // no-op for backwards compatibility
diff --git a/src/navigation/admin.js b/src/navigation/admin.js
index 6cfba3b738..04ecd7bc43 100644
--- a/src/navigation/admin.js
+++ b/src/navigation/admin.js
@@ -78,7 +78,7 @@ async function getAvailable() {
return item;
});
- return await plugins.fireHook('filter:navigation.available', core);
+ return await plugins.hooks.fire('filter:navigation.available', core);
}
require('../promisify')(admin);
diff --git a/src/notifications.js b/src/notifications.js
index 72e6e7ee07..f4f526c6d5 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -38,7 +38,7 @@ Notifications.privilegedTypes = [
];
Notifications.getAllNotificationTypes = async function () {
- const results = await plugins.fireHook('filter:user.notificationTypes', {
+ const results = await plugins.hooks.fire('filter:user.notificationTypes', {
types: Notifications.baseTypes.slice(),
privilegedTypes: Notifications.privilegedTypes.slice(),
});
@@ -213,7 +213,7 @@ async function pushToUids(uids, notification) {
// Remove uid from recipients list if they have blocked the user triggering the notification
uids = await User.blocks.filterUids(notification.from, uids);
- const data = await plugins.fireHook('filter:notification.push', { notification: notification, uids: uids });
+ const data = await plugins.hooks.fire('filter:notification.push', { notification: notification, uids: uids });
if (!data || !data.notification || !data.uids || !data.uids.length) {
return;
}
@@ -227,7 +227,7 @@ async function pushToUids(uids, notification) {
sendNotification(results.uidsToNotify),
sendEmail(results.uidsToEmail),
]);
- plugins.fireHook('action:notification.pushed', {
+ plugins.hooks.fire('action:notification.pushed', {
notification: notification,
uids: results.uidsToNotify,
uidsNotified: results.uidsToNotify,
@@ -419,7 +419,7 @@ Notifications.merge = async function (notifications) {
return notifications;
}, notifications);
- const data = await plugins.fireHook('filter:notifications.merge', {
+ const data = await plugins.hooks.fire('filter:notifications.merge', {
notifications: notifications,
});
return data && data.notifications;
diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js
index 6ce56fd988..a2509b35dc 100644
--- a/src/plugins/hooks.js
+++ b/src/plugins/hooks.js
@@ -3,210 +3,213 @@
const winston = require('winston');
const async = require('async');
const utils = require('../utils');
+const plugins = require('.');
-module.exports = function (Plugins) {
- Plugins.deprecatedHooks = {
- 'filter:privileges:isUserAllowedTo': 'filter:privileges:isAllowedTo',
- };
+const Hooks = {};
+module.exports = Hooks;
- Plugins.internals = {
- _register: function (data) {
- Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
- Plugins.loadedHooks[data.hook].push(data);
- },
- };
+Hooks.deprecatedHooks = {
+ 'filter:privileges:isUserAllowedTo': 'filter:privileges:isAllowedTo', // 👋 @ 1.16.0
+ 'filter:router.page': 'response:router.page', // 👋 @ 2.0.0
+};
- const hookTypeToMethod = {
- filter: fireFilterHook,
- action: fireActionHook,
- static: fireStaticHook,
- response: fireResponseHook,
- };
+Hooks.internals = {
+ _register: function (data) {
+ plugins.loadedHooks[data.hook] = plugins.loadedHooks[data.hook] || [];
+ plugins.loadedHooks[data.hook].push(data);
+ },
+};
- /*
- `data` is an object consisting of (* is required):
- `data.hook`*, the name of the NodeBB hook
- `data.method`*, the method called in that plugin (can be an array of functions)
- `data.priority`, the relative priority of the method when it is eventually called (default: 10)
- */
- Plugins.registerHook = function (id, data) {
- if (!data.hook || !data.method) {
- winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook/method', data);
- return;
- }
+const hookTypeToMethod = {
+ filter: fireFilterHook,
+ action: fireActionHook,
+ static: fireStaticHook,
+ response: fireResponseHook,
+};
- // `hasOwnProperty` needed for hooks with no alternative (set to null)
- if (Plugins.deprecatedHooks.hasOwnProperty(data.hook)) {
- const deprecated = Plugins.deprecatedHooks[data.hook];
+/*
+ `data` is an object consisting of (* is required):
+ `data.hook`*, the name of the NodeBB hook
+ `data.method`*, the method called in that plugin (can be an array of functions)
+ `data.priority`, the relative priority of the method when it is eventually called (default: 10)
+*/
+Hooks.register = function (id, data) {
+ if (!data.hook || !data.method) {
+ winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook/method', data);
+ return;
+ }
- if (deprecated) {
- winston.warn(`[plugins/${id}] Hook "${data.hook}" is deprecated, please use "${deprecated}" instead.`);
- } else {
- winston.warn(`[plugins/${id}] Hook "${data.hook}" is deprecated, there is no alternative.`);
- }
- }
+ // `hasOwnProperty` needed for hooks with no alternative (set to null)
+ if (Hooks.deprecatedHooks.hasOwnProperty(data.hook)) {
+ const deprecated = Hooks.deprecatedHooks[data.hook];
- data.id = id;
- if (!data.priority) {
- data.priority = 10;
- }
-
- if (Array.isArray(data.method) && data.method.every(method => typeof method === 'function' || typeof method === 'string')) {
- // Go go gadget recursion!
- data.method.forEach(function (method) {
- const singularData = { ...data, method: method };
- Plugins.registerHook(id, singularData);
- });
- } else if (typeof data.method === 'string' && data.method.length > 0) {
- const method = data.method.split('.').reduce(function (memo, prop) {
- if (memo && memo[prop]) {
- return memo[prop];
- }
- // Couldn't find method by path, aborting
- return null;
- }, Plugins.libraries[data.id]);
-
- // Write the actual method reference to the hookObj
- data.method = method;
-
- Plugins.internals._register(data);
- } else if (typeof data.method === 'function') {
- Plugins.internals._register(data);
+ if (deprecated) {
+ winston.warn(`[plugins/${id}] Hook "${data.hook}" is deprecated, please use "${deprecated}" instead.`);
} else {
- winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
+ winston.warn(`[plugins/${id}] Hook "${data.hook}" is deprecated, there is no alternative.`);
}
- };
+ }
- Plugins.unregisterHook = function (id, hook, method) {
- var hooks = Plugins.loadedHooks[hook] || [];
- Plugins.loadedHooks[hook] = hooks.filter(function (hookData) {
- return hookData && hookData.id !== id && hookData.method !== method;
+ data.id = id;
+ if (!data.priority) {
+ data.priority = 10;
+ }
+
+ if (Array.isArray(data.method) && data.method.every(method => typeof method === 'function' || typeof method === 'string')) {
+ // Go go gadget recursion!
+ data.method.forEach(function (method) {
+ const singularData = { ...data, method: method };
+ Hooks.register(id, singularData);
});
- };
-
- Plugins.fireHook = async function (hook, params) {
- const hookList = Plugins.loadedHooks[hook];
- const hookType = hook.split(':')[0];
- if (global.env === 'development' && hook !== 'action:plugins.firehook') {
- winston.verbose('[plugins/fireHook] ' + hook);
- }
-
- if (!hookTypeToMethod[hookType]) {
- winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
- return;
- }
- const result = await hookTypeToMethod[hookType](hook, hookList, params);
-
- if (hook !== 'action:plugins.firehook') {
- Plugins.fireHook('action:plugins.firehook', { hook: hook, params: params });
- }
- if (result !== undefined) {
- return result;
- }
- };
-
- async function fireFilterHook(hook, hookList, params) {
- if (!Array.isArray(hookList) || !hookList.length) {
- return params;
- }
-
- return await async.reduce(hookList, params, function (params, hookObj, next) {
- if (typeof hookObj.method !== 'function') {
- if (global.env === 'development') {
- winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
- }
- return next(null, params);
+ } else if (typeof data.method === 'string' && data.method.length > 0) {
+ const method = data.method.split('.').reduce(function (memo, prop) {
+ if (memo && memo[prop]) {
+ return memo[prop];
}
- const returned = hookObj.method(params, next);
+ // Couldn't find method by path, aborting
+ return null;
+ }, plugins.libraries[data.id]);
+
+ // Write the actual method reference to the hookObj
+ data.method = method;
+
+ Hooks.internals._register(data);
+ } else if (typeof data.method === 'function') {
+ Hooks.internals._register(data);
+ } else {
+ winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
+ }
+};
+
+Hooks.unregister = function (id, hook, method) {
+ var hooks = plugins.loadedHooks[hook] || [];
+ plugins.loadedHooks[hook] = hooks.filter(function (hookData) {
+ return hookData && hookData.id !== id && hookData.method !== method;
+ });
+};
+
+Hooks.fire = async function (hook, params) {
+ const hookList = plugins.loadedHooks[hook];
+ const hookType = hook.split(':')[0];
+ if (global.env === 'development' && hook !== 'action:plugins.fireHook') {
+ winston.verbose('[plugins/fireHook] ' + hook);
+ }
+
+ if (!hookTypeToMethod[hookType]) {
+ winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
+ return;
+ }
+ const result = await hookTypeToMethod[hookType](hook, hookList, params);
+
+ if (hook !== 'action:plugins.fireHook') {
+ Hooks.fire('action:plugins.fireHook', { hook: hook, params: params });
+ }
+ if (result !== undefined) {
+ return result;
+ }
+};
+
+Hooks.hasListeners = function (hook) {
+ return !!(plugins.loadedHooks[hook] && plugins.loadedHooks[hook].length > 0);
+};
+
+async function fireFilterHook(hook, hookList, params) {
+ if (!Array.isArray(hookList) || !hookList.length) {
+ return params;
+ }
+
+ return await async.reduce(hookList, params, function (params, hookObj, next) {
+ if (typeof hookObj.method !== 'function') {
+ if (global.env === 'development') {
+ winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
+ }
+ return next(null, params);
+ }
+ const returned = hookObj.method(params, next);
+ if (utils.isPromise(returned)) {
+ returned.then(
+ payload => setImmediate(next, null, payload),
+ err => setImmediate(next, err)
+ );
+ }
+ });
+}
+
+async function fireActionHook(hook, hookList, params) {
+ if (!Array.isArray(hookList) || !hookList.length) {
+ return;
+ }
+ for (const hookObj of hookList) {
+ if (typeof hookObj.method !== 'function') {
+ if (global.env === 'development') {
+ winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
+ }
+ } else {
+ /* eslint-disable no-await-in-loop */
+ await hookObj.method(params);
+ }
+ }
+}
+
+async function fireStaticHook(hook, hookList, params) {
+ if (!Array.isArray(hookList) || !hookList.length) {
+ return;
+ }
+ // don't bubble errors from these hooks, so bad plugins don't stop startup
+ const noErrorHooks = ['static:app.load', 'static:assets.prepare', 'static:app.preload'];
+ await async.each(hookList, function (hookObj, next) {
+ if (typeof hookObj.method !== 'function') {
+ return next();
+ }
+
+ let timedOut = false;
+ const timeoutId = setTimeout(function () {
+ winston.warn('[plugins] Callback timed out, hook \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
+ timedOut = true;
+ next();
+ }, 5000);
+
+ const callback = (err) => {
+ clearTimeout(timeoutId);
+ if (err) {
+ winston.error('[plugins] Error executing \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
+ winston.error(err.stack);
+ }
+ if (!timedOut) {
+ next(noErrorHooks.includes(hook) ? null : err);
+ }
+ };
+ try {
+ const returned = hookObj.method(params, callback);
if (utils.isPromise(returned)) {
returned.then(
- payload => setImmediate(next, null, payload),
- err => setImmediate(next, err)
+ payload => setImmediate(callback, null, payload),
+ err => setImmediate(callback, err)
);
}
- });
- }
+ } catch (err) {
+ callback(err);
+ }
+ });
+}
- async function fireActionHook(hook, hookList, params) {
- if (!Array.isArray(hookList) || !hookList.length) {
+async function fireResponseHook(hook, hookList, params) {
+ if (!Array.isArray(hookList) || !hookList.length) {
+ return;
+ }
+ await async.eachSeries(hookList, async (hookObj) => {
+ if (typeof hookObj.method !== 'function') {
+ if (global.env === 'development') {
+ winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
+ }
return;
}
- for (const hookObj of hookList) {
- if (typeof hookObj.method !== 'function') {
- if (global.env === 'development') {
- winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
- }
- } else {
- /* eslint-disable no-await-in-loop */
- await hookObj.method(params);
- }
- }
- }
- async function fireStaticHook(hook, hookList, params) {
- if (!Array.isArray(hookList) || !hookList.length) {
+ // Skip remaining hooks if headers have been sent
+ if (params.res.headersSent) {
return;
}
- // don't bubble errors from these hooks, so bad plugins don't stop startup
- const noErrorHooks = ['static:app.load', 'static:assets.prepare', 'static:app.preload'];
- await async.each(hookList, function (hookObj, next) {
- if (typeof hookObj.method !== 'function') {
- return next();
- }
- let timedOut = false;
- const timeoutId = setTimeout(function () {
- winston.warn('[plugins] Callback timed out, hook \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
- timedOut = true;
- next();
- }, 5000);
-
- const callback = (err) => {
- clearTimeout(timeoutId);
- if (err) {
- winston.error('[plugins] Error executing \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
- winston.error(err.stack);
- }
- if (!timedOut) {
- next(noErrorHooks.includes(hook) ? null : err);
- }
- };
- try {
- const returned = hookObj.method(params, callback);
- if (utils.isPromise(returned)) {
- returned.then(
- payload => setImmediate(callback, null, payload),
- err => setImmediate(callback, err)
- );
- }
- } catch (err) {
- callback(err);
- }
- });
- }
-
- async function fireResponseHook(hook, hookList, params) {
- if (!Array.isArray(hookList) || !hookList.length) {
- return;
- }
- await async.eachSeries(hookList, async (hookObj) => {
- if (typeof hookObj.method !== 'function') {
- if (global.env === 'development') {
- winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
- }
- return;
- }
-
- // Skip remaining hooks if headers have been sent
- if (params.res.headersSent) {
- return;
- }
-
- await hookObj.method(params);
- });
- }
-
- Plugins.hasListeners = function (hook) {
- return !!(Plugins.loadedHooks[hook] && Plugins.loadedHooks[hook].length > 0);
- };
-};
+ await hookObj.method(params);
+ });
+}
diff --git a/src/plugins/index.js b/src/plugins/index.js
index 172dceedec..e0097aebfd 100644
--- a/src/plugins/index.js
+++ b/src/plugins/index.js
@@ -21,9 +21,16 @@ const Plugins = module.exports;
require('./install')(Plugins);
require('./load')(Plugins);
-require('./hooks')(Plugins);
require('./usage')(Plugins);
Plugins.data = require('./data');
+Plugins.hooks = require('./hooks');
+
+// Backwards compatibility for hooks, remove in v1.16.0
+Plugins.registerHook = Plugins.hooks.register;
+Plugins.unregisterHook = Plugins.hooks.unregister;
+Plugins.fireHook = Plugins.hooks.fire;
+Plugins.hasListeners = Plugins.hooks.hasListeners;
+// end
Plugins.getPluginPaths = Plugins.data.getPluginPaths;
Plugins.loadPluginInfo = Plugins.data.loadPluginInfo;
@@ -138,7 +145,7 @@ Plugins.reload = async function () {
Plugins.reloadRoutes = async function (params) {
var controllers = require('../controllers');
- await Plugins.fireHook('static:app.load', { app: app, router: params.router, middleware: middleware, controllers: controllers });
+ await Plugins.hooks.fire('static:app.load', { app: app, router: params.router, middleware: middleware, controllers: controllers });
winston.verbose('[plugins] All plugins reloaded and rerouted');
};
diff --git a/src/plugins/install.js b/src/plugins/install.js
index 9394cb784a..49d0068b7e 100644
--- a/src/plugins/install.js
+++ b/src/plugins/install.js
@@ -64,7 +64,7 @@ module.exports = function (Plugins) {
await db.sortedSetAdd('plugins:active', count, id);
}
meta.reloadRequired = true;
- Plugins.fireHook(isActive ? 'action:plugin.deactivate' : 'action:plugin.activate', { id: id });
+ Plugins.hooks.fire(isActive ? 'action:plugin.deactivate' : 'action:plugin.activate', { id: id });
return { id: id, active: !isActive };
};
@@ -100,7 +100,7 @@ module.exports = function (Plugins) {
}
await runPackageManagerCommandAsync(type, id, version || 'latest');
const pluginData = await Plugins.get(id);
- Plugins.fireHook('action:plugin.' + type, { id: id, version: version });
+ Plugins.hooks.fire('action:plugin.' + type, { id: id, version: version });
return pluginData;
}
diff --git a/src/plugins/load.js b/src/plugins/load.js
index 2c639659b3..8977e40900 100644
--- a/src/plugins/load.js
+++ b/src/plugins/load.js
@@ -160,7 +160,7 @@ module.exports = function (Plugins) {
}
if (Array.isArray(pluginData.hooks)) {
- pluginData.hooks.forEach(hook => Plugins.registerHook(pluginData.id, hook));
+ pluginData.hooks.forEach(hook => Plugins.hooks.register(pluginData.id, hook));
}
} catch (err) {
winston.warn('[plugins] Unable to load library for: ' + pluginData.id);
diff --git a/src/posts/bookmarks.js b/src/posts/bookmarks.js
index fd50af9465..51ab7677bd 100644
--- a/src/posts/bookmarks.js
+++ b/src/posts/bookmarks.js
@@ -41,7 +41,7 @@ module.exports = function (Posts) {
postData.bookmarks = await db.setCount('pid:' + pid + ':users_bookmarked');
await Posts.setPostField(pid, 'bookmarks', postData.bookmarks);
- plugins.fireHook('action:post.' + type, {
+ plugins.hooks.fire('action:post.' + type, {
pid: pid,
uid: uid,
owner: postData.uid,
diff --git a/src/posts/create.js b/src/posts/create.js
index babbce94f2..91da2c2903 100644
--- a/src/posts/create.js
+++ b/src/posts/create.js
@@ -47,7 +47,7 @@ module.exports = function (Posts) {
postData.handle = data.handle;
}
- let result = await plugins.fireHook('filter:post.create', { post: postData, data: data });
+ let result = await plugins.hooks.fire('filter:post.create', { post: postData, data: data });
postData = result.post;
await db.setObject('post:' + postData.pid, postData);
@@ -65,9 +65,9 @@ module.exports = function (Posts) {
Posts.uploads.sync(postData.pid),
]);
- result = await plugins.fireHook('filter:post.get', { post: postData, uid: data.uid });
+ result = await plugins.hooks.fire('filter:post.get', { post: postData, uid: data.uid });
result.post.isMain = isMain;
- plugins.fireHook('action:post.save', { post: _.clone(result.post) });
+ plugins.hooks.fire('action:post.save', { post: _.clone(result.post) });
return result.post;
};
diff --git a/src/posts/data.js b/src/posts/data.js
index e4894fc61e..dcd50b32b5 100644
--- a/src/posts/data.js
+++ b/src/posts/data.js
@@ -17,7 +17,7 @@ module.exports = function (Posts) {
}
const keys = pids.map(pid => 'post:' + pid);
const postData = await (fields.length ? db.getObjectsFields(keys, fields) : db.getObjects(keys));
- const result = await plugins.fireHook('filter:post.getFields', {
+ const result = await plugins.hooks.fire('filter:post.getFields', {
pids: pids,
posts: postData,
fields: fields,
@@ -51,7 +51,7 @@ module.exports = function (Posts) {
Posts.setPostFields = async function (pid, data) {
await db.setObject('post:' + pid, data);
- plugins.fireHook('action:post.setFields', { data: { ...data, pid } });
+ plugins.hooks.fire('action:post.setFields', { data: { ...data, pid } });
};
};
diff --git a/src/posts/delete.js b/src/posts/delete.js
index e1429cd854..0504eec5f4 100644
--- a/src/posts/delete.js
+++ b/src/posts/delete.js
@@ -22,7 +22,7 @@ module.exports = function (Posts) {
async function deleteOrRestore(type, pid, uid) {
const isDeleting = type === 'delete';
- await plugins.fireHook('filter:post.' + type, { pid: pid, uid: uid });
+ await plugins.hooks.fire('filter:post.' + type, { pid: pid, uid: uid });
await Posts.setPostFields(pid, {
deleted: isDeleting ? 1 : 0,
deleterUid: isDeleting ? uid : 0,
@@ -38,7 +38,7 @@ module.exports = function (Posts) {
db.sortedSetAdd('cid:' + topicData.cid + ':pids', postData.timestamp, pid),
]);
await categories.updateRecentTidForCid(postData.cid);
- plugins.fireHook('action:post.' + type, { post: _.clone(postData), uid: uid });
+ plugins.hooks.fire('action:post.' + type, { post: _.clone(postData), uid: uid });
if (type === 'delete') {
await flags.resolveFlag('post', pid, uid);
}
@@ -52,7 +52,7 @@ module.exports = function (Posts) {
}
const topicData = await topics.getTopicFields(postData.tid, ['tid', 'cid', 'pinned']);
postData.cid = topicData.cid;
- await plugins.fireHook('filter:post.purge', { post: postData, pid: pid, uid: uid });
+ await plugins.hooks.fire('filter:post.purge', { post: postData, pid: pid, uid: uid });
await Promise.all([
deletePostFromTopicUserNotification(postData, topicData),
deletePostFromCategoryRecentPosts(postData),
@@ -64,7 +64,7 @@ module.exports = function (Posts) {
Posts.uploads.dissociateAll(pid),
]);
await flags.resolveFlag('post', pid, uid);
- plugins.fireHook('action:post.purge', { post: postData, uid: uid });
+ plugins.hooks.fire('action:post.purge', { post: postData, uid: uid });
await db.delete('post:' + pid);
};
diff --git a/src/posts/diffs.js b/src/posts/diffs.js
index eeb44927da..79bdd5223a 100644
--- a/src/posts/diffs.js
+++ b/src/posts/diffs.js
@@ -55,7 +55,7 @@ module.exports = function (Posts) {
const post = await postDiffLoad(pid, since, uid);
post.content = String(post.content || '');
- const result = await plugins.fireHook('filter:parse.post', { postData: post });
+ const result = await plugins.hooks.fire('filter:parse.post', { postData: post });
result.postData.content = translator.escape(result.postData.content);
return result.postData;
};
diff --git a/src/posts/edit.js b/src/posts/edit.js
index 9b3a00775a..667c1b0553 100644
--- a/src/posts/edit.js
+++ b/src/posts/edit.js
@@ -40,7 +40,7 @@ module.exports = function (Posts) {
editPostData.handle = data.handle;
}
- const result = await plugins.fireHook('filter:post.edit', {
+ const result = await plugins.hooks.fire('filter:post.edit', {
req: data.req,
post: editPostData,
data: data,
@@ -79,7 +79,7 @@ module.exports = function (Posts) {
nid: 'edit_post:' + data.pid + ':uid:' + data.uid,
});
- plugins.fireHook('action:post.edit', { post: _.clone(returnPostData), data: data, uid: data.uid });
+ plugins.hooks.fire('action:post.edit', { post: _.clone(returnPostData), data: data, uid: data.uid });
require('./cache').del(String(postData.pid));
pubsub.publish('post:edit', String(postData.pid));
@@ -122,7 +122,11 @@ module.exports = function (Posts) {
newTopicData.title = title;
newTopicData.slug = tid + '/' + (slugify(title) || 'topic');
}
- newTopicData.thumb = data.thumb || '';
+
+ if (data.thumb) {
+ await topics.resizeAndUploadThumb(data);
+ newTopicData.thumb = data.thumb;
+ }
data.tags = data.tags || [];
@@ -134,7 +138,7 @@ module.exports = function (Posts) {
}
await topics.validateTags(data.tags, topicData.cid);
- const results = await plugins.fireHook('filter:topic.edit', {
+ const results = await plugins.hooks.fire('filter:topic.edit', {
req: data.req,
topic: newTopicData,
data: data,
@@ -147,7 +151,7 @@ module.exports = function (Posts) {
newTopicData.oldTitle = topicData.title;
newTopicData.timestamp = topicData.timestamp;
const renamed = translator.escape(validator.escape(String(title))) !== topicData.title;
- plugins.fireHook('action:topic.edit', { topic: newTopicData, uid: data.uid });
+ plugins.hooks.fire('action:topic.edit', { topic: newTopicData, uid: data.uid });
return {
tid: tid,
cid: newTopicData.cid,
diff --git a/src/posts/index.js b/src/posts/index.js
index a17556644b..a145976acd 100644
--- a/src/posts/index.js
+++ b/src/posts/index.js
@@ -48,7 +48,7 @@ Posts.getPostsByPids = async function (pids, uid) {
let posts = await Posts.getPostsData(pids);
posts = await Promise.all(posts.map(p => Posts.parsePost(p)));
posts = await user.blocks.filter(uid, posts);
- const data = await plugins.fireHook('filter:post.getPosts', { posts: posts, uid: uid });
+ const data = await plugins.hooks.fire('filter:post.getPosts', { posts: posts, uid: uid });
if (!data || !Array.isArray(data.posts)) {
return [];
}
diff --git a/src/posts/parse.js b/src/posts/parse.js
index 6b2d59600e..131b47e221 100644
--- a/src/posts/parse.js
+++ b/src/posts/parse.js
@@ -60,7 +60,7 @@ module.exports = function (Posts) {
return postData;
}
- const data = await plugins.fireHook('filter:parse.post', { postData: postData });
+ const data = await plugins.hooks.fire('filter:parse.post', { postData: postData });
data.postData.content = translator.escape(data.postData.content);
if (data.postData.pid) {
cache.set(pid, data.postData.content);
@@ -70,7 +70,7 @@ module.exports = function (Posts) {
Posts.parseSignature = async function (userData, uid) {
userData.signature = sanitizeSignature(userData.signature || '');
- return await plugins.fireHook('filter:parse.signature', { userData: userData, uid: uid });
+ return await plugins.hooks.fire('filter:parse.signature', { userData: userData, uid: uid });
};
Posts.relativeToAbsolute = function (content, regex) {
@@ -121,11 +121,11 @@ module.exports = function (Posts) {
});
// Some plugins might need to adjust or whitelist their own tags...
- sanitizeConfig = await plugins.fireHook('filter:sanitize.config', sanitizeConfig);
+ sanitizeConfig = await plugins.hooks.fire('filter:sanitize.config', sanitizeConfig);
};
Posts.registerHooks = () => {
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:parse.post',
method: async (data) => {
data.postData.content = Posts.sanitize(data.postData.content);
@@ -133,17 +133,17 @@ module.exports = function (Posts) {
},
});
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:parse.raw',
method: async content => Posts.sanitize(content),
});
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:parse.aboutme',
method: async content => Posts.sanitize(content),
});
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:parse.signature',
method: async (data) => {
data.userData.signature = Posts.sanitize(data.userData.signature);
diff --git a/src/posts/queue.js b/src/posts/queue.js
index 9fd302eeed..bd8121399e 100644
--- a/src/posts/queue.js
+++ b/src/posts/queue.js
@@ -18,7 +18,7 @@ module.exports = function (Posts) {
const userData = await user.getUserFields(uid, ['uid', 'reputation', 'postcount']);
const isMemberOfExempt = await groups.isMemberOfAny(userData.uid, meta.config.groupsExemptFromPostQueue);
const shouldQueue = meta.config.postQueue && !isMemberOfExempt && (!userData.uid || userData.reputation < meta.config.postQueueReputationThreshold || userData.postcount <= 0);
- const result = await plugins.fireHook('filter:post.shouldQueue', {
+ const result = await plugins.hooks.fire('filter:post.shouldQueue', {
shouldQueue: !!shouldQueue,
uid: uid,
data: data,
@@ -57,7 +57,7 @@ module.exports = function (Posts) {
type: type,
data: data,
};
- payload = await plugins.fireHook('filter:post-queue.save', payload);
+ payload = await plugins.hooks.fire('filter:post-queue.save', payload);
payload.data = JSON.stringify(data);
await db.sortedSetAdd('post:queue', now, id);
diff --git a/src/posts/summary.js b/src/posts/summary.js
index 0b4e5c8467..6a0bda8c2b 100644
--- a/src/posts/summary.js
+++ b/src/posts/summary.js
@@ -54,7 +54,7 @@ module.exports = function (Posts) {
posts = posts.filter(post => tidToTopic[post.tid]);
posts = await parsePosts(posts, options);
- const result = await plugins.fireHook('filter:post.getPostSummaryByPids', { posts: posts, uid: uid });
+ const result = await plugins.hooks.fire('filter:post.getPostSummaryByPids', { posts: posts, uid: uid });
return result.posts;
};
diff --git a/src/posts/user.js b/src/posts/user.js
index 6547e9f295..fef2eadd58 100644
--- a/src/posts/user.js
+++ b/src/posts/user.js
@@ -36,7 +36,7 @@ module.exports = function (Posts) {
const [isMemberOfGroups, signature, customProfileInfo] = await Promise.all([
checkGroupMembership(userData.uid, userData.groupTitleArray),
parseSignature(userData, uid, canUseSignature),
- plugins.fireHook('filter:posts.custom_profile_info', { profile: [], uid: userData.uid }),
+ plugins.hooks.fire('filter:posts.custom_profile_info', { profile: [], uid: userData.uid }),
]);
if (isMemberOfGroups && userData.groupTitleArray) {
@@ -49,7 +49,7 @@ module.exports = function (Posts) {
userData.signature = signature;
userData.custom_profile_info = customProfileInfo.profile;
- return await plugins.fireHook('filter:posts.modifyUserInfo', userData);
+ return await plugins.hooks.fire('filter:posts.modifyUserInfo', userData);
}));
};
@@ -94,7 +94,7 @@ module.exports = function (Posts) {
'signature', 'banned', 'banned:expire', 'status',
'lastonline', 'groupTitle',
];
- const result = await plugins.fireHook('filter:posts.addUserFields', {
+ const result = await plugins.hooks.fire('filter:posts.addUserFields', {
fields: fields,
uid: uid,
uids: uids,
@@ -166,7 +166,7 @@ module.exports = function (Posts) {
updateTopicPosters(postData, toUid),
]);
- plugins.fireHook('action:post.changeOwner', {
+ plugins.hooks.fire('action:post.changeOwner', {
posts: _.cloneDeep(postData),
toUid: toUid,
});
@@ -228,7 +228,7 @@ module.exports = function (Posts) {
]);
const changedTopics = mainPosts.map(p => tidToTopic[p.tid]);
- plugins.fireHook('action:topic.changeOwner', {
+ plugins.hooks.fire('action:topic.changeOwner', {
topics: _.cloneDeep(changedTopics),
toUid: toUid,
});
diff --git a/src/posts/votes.js b/src/posts/votes.js
index fd224695d4..81f340b314 100644
--- a/src/posts/votes.js
+++ b/src/posts/votes.js
@@ -144,7 +144,7 @@ module.exports = function (Posts) {
current = 'unvote';
}
- plugins.fireHook('action:post.' + hook, {
+ plugins.hooks.fire('action:post.' + hook, {
pid: pid,
uid: uid,
owner: owner,
@@ -251,7 +251,7 @@ module.exports = function (Posts) {
downvotes: postData.downvotes,
}),
]);
- plugins.fireHook('action:post.updatePostVoteCount', { post: postData });
+ plugins.hooks.fire('action:post.updatePostVoteCount', { post: postData });
};
async function updateTopicVoteCount(postData) {
diff --git a/src/privileges/admin.js b/src/privileges/admin.js
index 305ea98fde..31c0b3022d 100644
--- a/src/privileges/admin.js
+++ b/src/privileges/admin.js
@@ -120,14 +120,14 @@ module.exports = function (privileges) {
async function getLabels() {
return await utils.promiseParallel({
- users: plugins.fireHook('filter:privileges.admin.list_human', privilegeLabels.slice()),
- groups: plugins.fireHook('filter:privileges.admin.groups.list_human', privilegeLabels.slice()),
+ users: plugins.hooks.fire('filter:privileges.admin.list_human', privilegeLabels.slice()),
+ groups: plugins.hooks.fire('filter:privileges.admin.groups.list_human', privilegeLabels.slice()),
});
}
const keys = await utils.promiseParallel({
- users: plugins.fireHook('filter:privileges.admin.list', userPrivilegeList.slice()),
- groups: plugins.fireHook('filter:privileges.admin.groups.list', groupPrivilegeList.slice()),
+ users: plugins.hooks.fire('filter:privileges.admin.list', userPrivilegeList.slice()),
+ groups: plugins.hooks.fire('filter:privileges.admin.groups.list', groupPrivilegeList.slice()),
});
const payload = await utils.promiseParallel({
@@ -152,7 +152,7 @@ module.exports = function (privileges) {
const privData = _.zipObject(privileges.admin.userPrivilegeList, combined);
privData.superadmin = isAdministrator;
- return await plugins.fireHook('filter:privileges.admin.get', privData);
+ return await plugins.hooks.fire('filter:privileges.admin.get', privData);
};
privileges.admin.can = async function (privilege, uid) {
@@ -169,7 +169,7 @@ module.exports = function (privileges) {
privileges.admin.give = async function (privileges, groupName) {
await helpers.giveOrRescind(groups.join, privileges, 'admin', groupName);
- plugins.fireHook('action:privileges.admin.give', {
+ plugins.hooks.fire('action:privileges.admin.give', {
privileges: privileges,
groupNames: Array.isArray(groupName) ? groupName : [groupName],
});
@@ -177,7 +177,7 @@ module.exports = function (privileges) {
privileges.admin.rescind = async function (privileges, groupName) {
await helpers.giveOrRescind(groups.leave, privileges, 'admin', groupName);
- plugins.fireHook('action:privileges.admin.rescind', {
+ plugins.hooks.fire('action:privileges.admin.rescind', {
privileges: privileges,
groupNames: Array.isArray(groupName) ? groupName : [groupName],
});
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index b915f289da..1276bf6d3b 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -17,14 +17,14 @@ module.exports = function (privileges) {
privileges.categories.list = async function (cid) {
async function getLabels() {
return await utils.promiseParallel({
- users: plugins.fireHook('filter:privileges.list_human', privileges.privilegeLabels.slice()),
- groups: plugins.fireHook('filter:privileges.groups.list_human', privileges.privilegeLabels.slice()),
+ users: plugins.hooks.fire('filter:privileges.list_human', privileges.privilegeLabels.slice()),
+ groups: plugins.hooks.fire('filter:privileges.groups.list_human', privileges.privilegeLabels.slice()),
});
}
const keys = await utils.promiseParallel({
- users: plugins.fireHook('filter:privileges.list', privileges.userPrivilegeList.slice()),
- groups: plugins.fireHook('filter:privileges.groups.list', privileges.groupPrivilegeList.slice()),
+ users: plugins.hooks.fire('filter:privileges.list', privileges.userPrivilegeList.slice()),
+ groups: plugins.hooks.fire('filter:privileges.groups.list', privileges.groupPrivilegeList.slice()),
});
const payload = await utils.promiseParallel({
@@ -55,7 +55,7 @@ module.exports = function (privileges) {
const privData = _.zipObject(privs, combined);
const isAdminOrMod = isAdministrator || isModerator;
- return await plugins.fireHook('filter:privileges.categories.get', {
+ return await plugins.hooks.fire('filter:privileges.categories.get', {
...privData,
cid: cid,
uid: uid,
@@ -135,7 +135,7 @@ module.exports = function (privileges) {
privileges.categories.give = async function (privileges, cid, members) {
await helpers.giveOrRescind(groups.join, privileges, cid, members);
- plugins.fireHook('action:privileges.categories.give', {
+ plugins.hooks.fire('action:privileges.categories.give', {
privileges: privileges,
cids: Array.isArray(cid) ? cid : [cid],
members: Array.isArray(members) ? members : [members],
@@ -144,7 +144,7 @@ module.exports = function (privileges) {
privileges.categories.rescind = async function (privileges, cid, members) {
await helpers.giveOrRescind(groups.leave, privileges, cid, members);
- plugins.fireHook('action:privileges.categories.rescind', {
+ plugins.hooks.fire('action:privileges.categories.rescind', {
privileges: privileges,
cids: Array.isArray(cid) ? cid : [cid],
members: Array.isArray(members) ? members : [members],
diff --git a/src/privileges/global.js b/src/privileges/global.js
index 3585a432cc..44ad5ecd16 100644
--- a/src/privileges/global.js
+++ b/src/privileges/global.js
@@ -53,14 +53,14 @@ module.exports = function (privileges) {
privileges.global.list = async function () {
async function getLabels() {
return await utils.promiseParallel({
- users: plugins.fireHook('filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()),
- groups: plugins.fireHook('filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()),
+ users: plugins.hooks.fire('filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()),
+ groups: plugins.hooks.fire('filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()),
});
}
const keys = await utils.promiseParallel({
- users: plugins.fireHook('filter:privileges.global.list', privileges.global.userPrivilegeList.slice()),
- groups: plugins.fireHook('filter:privileges.global.groups.list', privileges.global.groupPrivilegeList.slice()),
+ users: plugins.hooks.fire('filter:privileges.global.list', privileges.global.userPrivilegeList.slice()),
+ groups: plugins.hooks.fire('filter:privileges.global.groups.list', privileges.global.groupPrivilegeList.slice()),
});
const payload = await utils.promiseParallel({
@@ -84,7 +84,7 @@ module.exports = function (privileges) {
const combined = userPrivileges.map(allowed => allowed || isAdministrator);
const privData = _.zipObject(privileges.global.userPrivilegeList, combined);
- return await plugins.fireHook('filter:privileges.global.get', privData);
+ return await plugins.hooks.fire('filter:privileges.global.get', privData);
};
privileges.global.can = async function (privilege, uid) {
@@ -101,7 +101,7 @@ module.exports = function (privileges) {
privileges.global.give = async function (privileges, groupName) {
await helpers.giveOrRescind(groups.join, privileges, 0, groupName);
- plugins.fireHook('action:privileges.global.give', {
+ plugins.hooks.fire('action:privileges.global.give', {
privileges: privileges,
groupNames: Array.isArray(groupName) ? groupName : [groupName],
});
@@ -109,7 +109,7 @@ module.exports = function (privileges) {
privileges.global.rescind = async function (privileges, groupName) {
await helpers.giveOrRescind(groups.leave, privileges, 0, groupName);
- plugins.fireHook('action:privileges.global.rescind', {
+ plugins.hooks.fire('action:privileges.global.rescind', {
privileges: privileges,
groupNames: Array.isArray(groupName) ? groupName : [groupName],
});
diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js
index cda8eca5b9..cf4c5fc998 100644
--- a/src/privileges/helpers.js
+++ b/src/privileges/helpers.js
@@ -22,7 +22,7 @@ helpers.isUsersAllowedTo = async function (privilege, uids, cid) {
groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege),
]);
const allowed = uids.map((uid, index) => hasUserPrivilege[index] || hasGroupPrivilege[index]);
- const result = await plugins.fireHook('filter:privileges:isUsersAllowedTo', { allowed: allowed, privilege: privilege, uids: uids, cid: cid });
+ const result = await plugins.hooks.fire('filter:privileges:isUsersAllowedTo', { allowed: allowed, privilege: privilege, uids: uids, cid: cid });
return result.allowed;
};
@@ -34,8 +34,8 @@ helpers.isAllowedTo = async function (privilege, uidOrGroupName, cid) {
allowed = await isAllowedToCids(privilege, uidOrGroupName, cid);
}
if (allowed) {
- ({ allowed } = await plugins.fireHook('filter:privileges:isUserAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
- ({ allowed } = await plugins.fireHook('filter:privileges:isAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
+ ({ allowed } = await plugins.hooks.fire('filter:privileges:isUserAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
+ ({ allowed } = await plugins.hooks.fire('filter:privileges:isAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
return allowed;
}
throw new Error('[[error:invalid-data]]');
diff --git a/src/privileges/posts.js b/src/privileges/posts.js
index f1274b892e..cfbfeedb1c 100644
--- a/src/privileges/posts.js
+++ b/src/privileges/posts.js
@@ -101,7 +101,7 @@ module.exports = function (privileges) {
((!post.topic.deleted && !post.deleted) || canViewDeleted[post.topic.cid] || results.isAdmin);
}).map(post => post.pid);
- const data = await plugins.fireHook('filter:privileges.posts.filter', {
+ const data = await plugins.hooks.fire('filter:privileges.posts.filter', {
privilege: privilege,
uid: uid,
pids: pids,
@@ -144,7 +144,7 @@ module.exports = function (privileges) {
results.pid = parseInt(pid, 10);
results.uid = uid;
- const result = await plugins.fireHook('filter:privileges.posts.edit', results);
+ const result = await plugins.hooks.fire('filter:privileges.posts.edit', results);
return { flag: result.edit && (result.owner || result.isMod), message: '[[error:no-privileges]]' };
};
diff --git a/src/privileges/topics.js b/src/privileges/topics.js
index 75a3e01fb3..ff5890740c 100644
--- a/src/privileges/topics.js
+++ b/src/privileges/topics.js
@@ -34,7 +34,7 @@ module.exports = function (privileges) {
const editable = isAdminOrMod;
const deletable = (privData['topics:delete'] && (isOwner || isModerator)) || isAdministrator;
- return await plugins.fireHook('filter:privileges.topics.get', {
+ return await plugins.hooks.fire('filter:privileges.topics.get', {
'topics:reply': (privData['topics:reply'] && ((!topicData.locked && !topicData.deleted) || isModerator)) || isAdministrator,
'topics:read': privData['topics:read'] || isAdministrator,
'topics:tag': privData['topics:tag'] || isAdministrator,
@@ -78,7 +78,7 @@ module.exports = function (privileges) {
tids = topicsData.filter(t => cidsSet.has(t.cid) && (!t.deleted || canViewDeleted[t.cid] || results.isAdmin)).map(t => t.tid);
- const data = await plugins.fireHook('filter:privileges.topics.filter', {
+ const data = await plugins.hooks.fire('filter:privileges.topics.filter', {
privilege: privilege,
uid: uid,
tids: tids,
diff --git a/src/privileges/users.js b/src/privileges/users.js
index 9a391f2f13..b23df4d198 100644
--- a/src/privileges/users.js
+++ b/src/privileges/users.js
@@ -64,7 +64,7 @@ module.exports = function (privileges) {
}
async function filterIsModerator(cid, uid, isModerator) {
- const data = await plugins.fireHook('filter:user.isModerator', { uid: uid, cid: cid, isModerator: isModerator });
+ const data = await plugins.hooks.fire('filter:user.isModerator', { uid: uid, cid: cid, isModerator: isModerator });
if ((Array.isArray(uid) || Array.isArray(cid)) && !Array.isArray(data.isModerator)) {
throw new Error('filter:user.isModerator - i/o mismatch');
}
@@ -82,7 +82,7 @@ module.exports = function (privileges) {
privileges.users.isAdministrator(uid),
]);
- const data = await plugins.fireHook('filter:user.canEdit', {
+ const data = await plugins.hooks.fire('filter:user.canEdit', {
isAdmin: isAdmin,
isGlobalMod: isGlobalMod,
isTargetAdmin: isTargetAdmin,
@@ -99,7 +99,7 @@ module.exports = function (privileges) {
privileges.users.isAdministrator(uid),
]);
- const data = await plugins.fireHook('filter:user.canBanUser', {
+ const data = await plugins.hooks.fire('filter:user.canBanUser', {
canBan: canBan && !isTargetAdmin,
callerUid: callerUid,
uid: uid,
@@ -114,7 +114,7 @@ module.exports = function (privileges) {
const privilegeName = privilege.split('-').map(word => word.slice(0, 1).toUpperCase() + word.slice(1)).join('');
let payload = { uid };
payload[`can${privilegeName}`] = await privileges.global.can(privilege, uid);
- payload = await plugins.fireHook(`filter:user.has${privilegeName}Privilege`, payload);
+ payload = await plugins.hooks.fire(`filter:user.has${privilegeName}Privilege`, payload);
return payload[`can${privilegeName}`];
}
};
diff --git a/src/rewards/admin.js b/src/rewards/admin.js
index 0377ed19e3..a2566768b3 100644
--- a/src/rewards/admin.js
+++ b/src/rewards/admin.js
@@ -37,9 +37,9 @@ rewards.delete = async function (data) {
rewards.get = async function () {
return await utils.promiseParallel({
active: getActiveRewards(),
- conditions: plugins.fireHook('filter:rewards.conditions', []),
- conditionals: plugins.fireHook('filter:rewards.conditionals', []),
- rewards: plugins.fireHook('filter:rewards.rewards', []),
+ conditions: plugins.hooks.fire('filter:rewards.conditions', []),
+ conditionals: plugins.hooks.fire('filter:rewards.conditionals', []),
+ rewards: plugins.hooks.fire('filter:rewards.rewards', []),
});
};
diff --git a/src/rewards/index.js b/src/rewards/index.js
index 7144c7c4ae..19d25168e3 100644
--- a/src/rewards/index.js
+++ b/src/rewards/index.js
@@ -64,7 +64,7 @@ async function checkCondition(reward, method) {
method = util.promisify(method);
}
const value = await method();
- const bool = await plugins.fireHook('filter:rewards.checkConditional:' + reward.conditional, { left: value, right: reward.value });
+ const bool = await plugins.hooks.fire('filter:rewards.checkConditional:' + reward.conditional, { left: value, right: reward.value });
return bool;
}
@@ -72,7 +72,7 @@ async function giveRewards(uid, rewards) {
const rewardData = await getRewardsByRewardData(rewards);
for (let i = 0; i < rewards.length; i++) {
/* eslint-disable no-await-in-loop */
- await plugins.fireHook('action:rewards.award:' + rewards[i].rid, { uid: uid, reward: rewardData[i] });
+ await plugins.hooks.fire('action:rewards.award:' + rewards[i].rid, { uid: uid, reward: rewardData[i] });
await db.sortedSetIncrBy('uid:' + uid + ':rewards', 1, rewards[i].id);
}
}
diff --git a/src/routes/authentication.js b/src/routes/authentication.js
index b2f994ea74..8de1915c21 100644
--- a/src/routes/authentication.js
+++ b/src/routes/authentication.js
@@ -79,9 +79,9 @@ Auth.reloadRoutes = async function (params) {
const router = params.router;
// Local Logins
- if (plugins.hasListeners('action:auth.overrideLogin')) {
+ if (plugins.hooks.hasListeners('action:auth.overrideLogin')) {
winston.warn('[authentication] Login override detected, skipping local login strategy.');
- plugins.fireHook('action:auth.overrideLogin');
+ plugins.hooks.fire('action:auth.overrideLogin');
} else {
passport.use(new passportLocal({ passReqToCallback: true }, controllers.authentication.localLogin));
}
@@ -91,7 +91,7 @@ Auth.reloadRoutes = async function (params) {
// Additional logins via SSO plugins
try {
- loginStrategies = await plugins.fireHook('filter:auth.init', loginStrategies);
+ loginStrategies = await plugins.hooks.fire('filter:auth.init', loginStrategies);
} catch (err) {
winston.error('[authentication] ' + err.stack);
}
diff --git a/src/routes/index.js b/src/routes/index.js
index faff537df5..514bcee696 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -99,7 +99,7 @@ module.exports = async function (app, middleware) {
var ensureLoggedIn = require('connect-ensure-login');
router.all('(/+api|/+api/*?)', middleware.prepareAPI);
- router.all('(/+api/admin|/+api/admin/*?)', middleware.admin.checkPrivileges);
+ router.all('(/+api/admin|/+api/admin/*?)', middleware.authenticate, middleware.admin.checkPrivileges);
router.all('(/+admin|/+admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.admin.checkPrivileges);
app.use(middleware.stripLeadingSlashes);
diff --git a/src/routes/write/index.js b/src/routes/write/index.js
index dcb5995bf1..539668d508 100644
--- a/src/routes/write/index.js
+++ b/src/routes/write/index.js
@@ -1,7 +1,7 @@
'use strict';
-const nconf = require('nconf');
const winston = require('winston');
+const meta = require('../../meta');
const plugins = require('../../plugins');
const middleware = require('../../middleware');
const helpers = require('../../controllers/helpers');
@@ -10,10 +10,19 @@ const Write = module.exports;
Write.reload = async (params) => {
const router = params.router;
+ let apiSettings = await meta.settings.get('core.api');
+ plugins.registerHook('core', {
+ hook: 'action:settings.set',
+ method: async (data) => {
+ if (data.plugin === 'core.api') {
+ apiSettings = await meta.settings.get('core.api');
+ }
+ },
+ });
router.use('/api/v3', function (req, res, next) {
// Require https if configured so
- if (nconf.get('secure') && req.protocol !== 'https') {
+ if (apiSettings.requireHttps === 'on') {
res.set('Upgrade', 'TLS/1.0, HTTP/1.1');
return helpers.formatApiResponse(426, res);
}
@@ -48,7 +57,7 @@ Write.reload = async (params) => {
* `/api/v3/plugins`.
*/
const pluginRouter = require('express').Router();
- await plugins.fireHook('static:api.routes', {
+ await plugins.hooks.fire('static:api.routes', {
router: pluginRouter,
middleware,
helpers,
diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js
index b46de2f365..744f83aa66 100644
--- a/src/routes/write/topics.js
+++ b/src/routes/write/topics.js
@@ -17,7 +17,7 @@ module.exports = function () {
setupApiRoute(router, 'put', '/:tid/state', [...middlewares], controllers.write.topics.restore);
setupApiRoute(router, 'delete', '/:tid/state', [...middlewares], controllers.write.topics.delete);
- setupApiRoute(router, 'put', '/:tid/pin', [...middlewares], controllers.write.topics.pin);
+ setupApiRoute(router, 'put', '/:tid/pin', [...middlewares, middleware.assert.topic], controllers.write.topics.pin);
setupApiRoute(router, 'delete', '/:tid/pin', [...middlewares], controllers.write.topics.unpin);
setupApiRoute(router, 'put', '/:tid/lock', [...middlewares], controllers.write.topics.lock);
diff --git a/src/search.js b/src/search.js
index e67f185d2f..043a69f12d 100644
--- a/src/search.js
+++ b/src/search.js
@@ -43,7 +43,7 @@ async function searchInContent(data) {
async function doSearch(type, searchIn) {
if (searchIn.includes(data.searchIn)) {
- return await plugins.fireHook('filter:search.query', {
+ return await plugins.hooks.fire('filter:search.query', {
index: type,
content: data.query,
matchWords: data.matchWords || 'all',
@@ -70,7 +70,7 @@ async function searchInContent(data) {
allPids = await privileges.posts.filter('topics:read', allPids, data.uid);
allPids = await filterAndSort(allPids, data);
- const metadata = await plugins.fireHook('filter:search.inContent', {
+ const metadata = await plugins.hooks.fire('filter:search.inContent', {
pids: allPids,
});
@@ -87,13 +87,13 @@ async function searchInContent(data) {
}
returnData.posts = await posts.getPostSummaryByPids(metadata.pids, data.uid, {});
- await plugins.fireHook('filter:search.contentGetResult', { result: returnData, data: data });
+ await plugins.hooks.fire('filter:search.contentGetResult', { result: returnData, data: data });
delete metadata.pids;
return Object.assign(returnData, metadata);
}
async function filterAndSort(pids, data) {
- if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hasListeners('filter:search.filterAndSort')) {
+ if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hooks.hasListeners('filter:search.filterAndSort')) {
return pids;
}
let postsData = await getMatchedPosts(pids, data);
@@ -108,7 +108,7 @@ async function filterAndSort(pids, data) {
sortPosts(postsData, data);
- const result = await plugins.fireHook('filter:search.filterAndSort', { pids: pids, posts: postsData, data: data });
+ const result = await plugins.hooks.fire('filter:search.filterAndSort', { pids: pids, posts: postsData, data: data });
return result.posts.map(post => post && post.pid);
}
diff --git a/src/sitemap.js b/src/sitemap.js
index f506a96a36..cb47e4bc57 100644
--- a/src/sitemap.js
+++ b/src/sitemap.js
@@ -54,7 +54,7 @@ sitemap.getPages = async function () {
priority: 0.4,
}];
- const data = await plugins.fireHook('filter:sitemap.getPages', { urls: urls });
+ const data = await plugins.hooks.fire('filter:sitemap.getPages', { urls: urls });
const smStream = new SitemapStream({ hostname: nconf.get('url') });
data.urls.forEach(url => smStream.write(url));
diff --git a/src/social.js b/src/social.js
index e7ef401696..802fa3e754 100644
--- a/src/social.js
+++ b/src/social.js
@@ -24,7 +24,7 @@ social.getPostSharing = async function () {
class: 'fa-twitter',
},
];
- networks = await plugins.fireHook('filter:social.posts', networks);
+ networks = await plugins.hooks.fire('filter:social.posts', networks);
const activated = await db.getSetMembers('social:posts.activated');
networks.forEach(function (network) {
network.activated = activated.includes(network.id);
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 9462f2afee..631914f05d 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -109,4 +109,13 @@ SocketAdmin.reloadAllSessions = function (socket, data, callback) {
callback();
};
+SocketAdmin.getServerTime = function (socket, data, callback) {
+ const now = new Date();
+
+ callback(null, {
+ timestamp: now.getTime(),
+ offset: now.getTimezoneOffset(),
+ });
+};
+
require('../promisify')(SocketAdmin);
diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js
index fa50939768..08643b2cdf 100644
--- a/src/socket.io/admin/categories.js
+++ b/src/socket.io/admin/categories.js
@@ -31,7 +31,7 @@ Categories.getAll = async function () {
'color', 'bgColor', 'backgroundImage', 'imageClass',
];
const categoriesData = await categories.getCategoriesFields(cids, fields);
- const result = await plugins.fireHook('filter:admin.categories.get', { categories: categoriesData, fields: fields });
+ const result = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields });
return categories.getTree(result.categories, 0);
};
diff --git a/src/socket.io/admin/config.js b/src/socket.io/admin/config.js
index 63ab6d149c..6ec194e2a6 100644
--- a/src/socket.io/admin/config.js
+++ b/src/socket.io/admin/config.js
@@ -38,7 +38,7 @@ Config.setMultiple = async function (socket, data) {
key: field,
value: data[field],
};
- plugins.fireHook('action:config.set', setting);
+ plugins.hooks.fire('action:config.set', setting);
logger.monitorConfig({ io: index.server }, setting);
}
}
diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js
index 9f20203f0f..4e1152b514 100644
--- a/src/socket.io/groups.js
+++ b/src/socket.io/groups.js
@@ -4,6 +4,7 @@ const groups = require('../groups');
const user = require('../user');
const utils = require('../utils');
const events = require('../events');
+const privileges = require('../privileges');
const api = require('../api');
const sockets = require('.');
@@ -241,12 +242,11 @@ SocketGroups.loadMore = async (socket, data) => {
};
SocketGroups.searchMembers = async (socket, data) => {
- const [isOwner, isMember, isAdmin] = await Promise.all([
- groups.ownership.isOwner(socket.uid, data.groupName),
- groups.isMember(socket.uid, data.groupName),
- user.isAdministrator(socket.uid),
- ]);
- if (!isOwner && !isMember && !isAdmin) {
+ if (!data.groupName) {
+ throw new Error('[[error:invalid-data]]');
+ }
+ await canSearchMembers(socket.uid, data.groupName);
+ if (!await privileges.global.can('search:users', socket.uid)) {
throw new Error('[[error:no-privileges]]');
}
return await groups.searchMembers({
@@ -260,18 +260,7 @@ SocketGroups.loadMoreMembers = async (socket, data) => {
if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
throw new Error('[[error:invalid-data]]');
}
- const [isHidden, isAdmin, isGlobalMod] = await Promise.all([
- groups.isHidden(data.groupName),
- user.isAdministrator(socket.uid),
- user.isGlobalModerator(socket.uid),
- ]);
- if (isHidden && !isAdmin && !isGlobalMod) {
- const isMember = await groups.isMember(socket.uid, data.groupName);
- if (!isMember) {
- throw new Error('[[error:no-privileges]]');
- }
- }
-
+ await canSearchMembers(socket.uid, data.groupName);
data.after = parseInt(data.after, 10);
const users = await groups.getOwnersAndMembers(data.groupName, socket.uid, data.after, data.after + 9);
return {
@@ -280,6 +269,20 @@ SocketGroups.loadMoreMembers = async (socket, data) => {
};
};
+async function canSearchMembers(uid, groupName) {
+ const [isHidden, isMember, isAdmin, isGlobalMod, viewGroups] = await Promise.all([
+ groups.isHidden(groupName),
+ groups.isMember(uid, groupName),
+ user.isAdministrator(uid),
+ user.isGlobalModerator(uid),
+ privileges.global.can('view:groups', uid),
+ ]);
+
+ if (!viewGroups || (isHidden && !isMember && !isAdmin && !isGlobalMod)) {
+ throw new Error('[[error:no-privileges]]');
+ }
+}
+
SocketGroups.cover = {};
SocketGroups.cover.update = async (socket, data) => {
diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js
index c5262fe210..08545b9093 100644
--- a/src/socket.io/helpers.js
+++ b/src/socket.io/helpers.js
@@ -48,7 +48,7 @@ async function notifyUids(uid, uids, type, result) {
uids = filterTidCidIgnorers(watchStateUids, watchStates);
uids = await user.blocks.filterUids(uid, uids);
uids = await user.blocks.filterUids(post.topic.uid, uids);
- const data = await plugins.fireHook('filter:sockets.sendNewPostToUids', { uidsTo: uids, uidFrom: uid, type: type });
+ const data = await plugins.hooks.fire('filter:sockets.sendNewPostToUids', { uidsTo: uids, uidFrom: uid, type: type });
post.ip = undefined;
diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index f4e802c711..9dad7cda4b 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -141,8 +141,7 @@ async function onMessage(socket, payload) {
});
}
} catch (err) {
- const event = JSON.stringify({ eventName, params });
- winston.error(event + '\n' + (err.stack ? err.stack : err.message));
+ winston.error(eventName + '\n' + (err.stack ? err.stack : err.message));
callback({ message: err.message });
}
}
@@ -180,7 +179,7 @@ async function validateSession(socket) {
if (!sessionData) {
throw new Error('[[error:invalid-session]]');
}
- const result = await plugins.fireHook('static:sockets.validateSession', {
+ const result = await plugins.hooks.fire('static:sockets.validateSession', {
req: req,
socket: socket,
session: sessionData,
diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js
index e9cd3f00e4..75ea50c4f4 100644
--- a/src/socket.io/meta.js
+++ b/src/socket.io/meta.js
@@ -60,14 +60,4 @@ function leaveCurrentRoom(socket) {
}
}
-SocketMeta.getServerTime = function (socket, data, callback) {
- // Returns server time in milliseconds
- const now = new Date();
-
- callback(null, {
- timestamp: now.getTime(),
- offset: now.getTimezoneOffset(),
- });
-};
-
module.exports = SocketMeta;
diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js
index 5e9f087281..f9057d523a 100644
--- a/src/socket.io/modules.js
+++ b/src/socket.io/modules.js
@@ -71,7 +71,7 @@ SocketModules.chats.send = async function (socket, data) {
if (!canChat) {
throw new Error('[[error:no-privileges]]');
}
- const results = await plugins.fireHook('filter:messaging.send', {
+ const results = await plugins.hooks.fire('filter:messaging.send', {
data: data,
uid: socket.uid,
});
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 0bdc4fc82f..9c5318198e 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -67,7 +67,7 @@ SocketPosts.getRawPost = async function (socket, pid) {
throw new Error('[[error:no-post]]');
}
postData.pid = pid;
- const result = await plugins.fireHook('filter:post.getRawPost', { uid: socket.uid, postData: postData });
+ const result = await plugins.hooks.fire('filter:post.getRawPost', { uid: socket.uid, postData: postData });
return result.postData.content;
};
@@ -182,7 +182,7 @@ SocketPosts.editQueuedContent = async function (socket, data) {
}
await posts.editQueuedContent(socket.uid, data);
if (data.content) {
- return await plugins.fireHook('filter:parse.post', { postData: data });
+ return await plugins.hooks.fire('filter:parse.post', { postData: data });
}
return { postData: data };
};
diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js
index b62f651e7a..2b6255a449 100644
--- a/src/socket.io/posts/tools.js
+++ b/src/socket.io/posts/tools.js
@@ -29,7 +29,7 @@ module.exports = function (SocketPosts) {
canFlag: privileges.posts.canFlag(data.pid, socket.uid),
flagged: flags.exists('post', data.pid, socket.uid), // specifically, whether THIS calling user flagged
bookmarked: posts.hasBookmarked(data.pid, socket.uid),
- tools: plugins.fireHook('filter:post.tools', { pid: data.pid, uid: socket.uid, tools: [] }),
+ tools: plugins.hooks.fire('filter:post.tools', { pid: data.pid, uid: socket.uid, tools: [] }),
postSharing: social.getActivePostSharing(),
history: posts.diffs.exists(data.pid),
canViewInfo: privileges.global.can('view:users:info', socket.uid),
diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js
index 361ecefdb6..e28ecf3502 100644
--- a/src/socket.io/topics/infinitescroll.js
+++ b/src/socket.io/topics/infinitescroll.js
@@ -15,7 +15,7 @@ module.exports = function (SocketTopics) {
const [userPrivileges, topicData] = await Promise.all([
privileges.topics.get(data.tid, socket.uid),
- topics.getTopicFields(data.tid, ['postcount', 'deleted']),
+ topics.getTopicFields(data.tid, ['postcount', 'deleted', 'uid']),
]);
if (!userPrivileges['topics:read'] || (topicData.deleted && !userPrivileges.view_deleted)) {
diff --git a/src/socket.io/topics/tools.js b/src/socket.io/topics/tools.js
index f4181a5d45..a4b6804576 100644
--- a/src/socket.io/topics/tools.js
+++ b/src/socket.io/topics/tools.js
@@ -27,7 +27,7 @@ module.exports = function (SocketTopics) {
throw new Error('[[error:no-privileges]]');
}
topicData.privileges = userPrivileges;
- const result = await plugins.fireHook('filter:topic.thread_tools', { topic: topicData, uid: socket.uid, tools: [] });
+ const result = await plugins.hooks.fire('filter:topic.thread_tools', { topic: topicData, uid: socket.uid, tools: [] });
result.topic.thread_tools = result.tools;
return result.topic;
};
diff --git a/src/socket.io/user.js b/src/socket.io/user.js
index bac1a08266..ed7466a377 100644
--- a/src/socket.io/user.js
+++ b/src/socket.io/user.js
@@ -37,6 +37,7 @@ SocketUser.exists = async function (socket, data) {
SocketUser.deleteAccount = async function (socket, data) {
sockets.warnDeprecated(socket, 'DELETE /api/v3/users/:uid/account');
+ data.uid = socket.uid;
await api.users.deleteAccount(socket, data);
};
@@ -100,7 +101,7 @@ SocketUser.reset.commit = async function (socket, data) {
const [uid] = await Promise.all([
db.getObjectField('reset:uid', data.code),
user.reset.commit(data.code, data.password),
- plugins.fireHook('action:password.reset', { uid: socket.uid }),
+ plugins.hooks.fire('action:password.reset', { uid: socket.uid }),
]);
await events.log({
diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js
index 21d2a453b8..5fe4b0ea98 100644
--- a/src/socket.io/user/picture.js
+++ b/src/socket.io/user/picture.js
@@ -25,7 +25,7 @@ module.exports = function (SocketUser) {
} else if (type === 'uploaded') {
picture = await user.getUserField(data.uid, 'uploadedpicture');
} else {
- const returnData = await plugins.fireHook('filter:user.getPicture', {
+ const returnData = await plugins.hooks.fire('filter:user.getPicture', {
uid: socket.uid,
type: type,
picture: undefined,
@@ -53,7 +53,7 @@ module.exports = function (SocketUser) {
// if current picture is uploaded picture, reset to user icon
picture: userData.uploadedpicture === userData.picture ? '' : userData.picture,
});
- plugins.fireHook('action:user.removeUploadedPicture', {
+ plugins.hooks.fire('action:user.removeUploadedPicture', {
callerUid: socket.uid,
uid: data.uid,
user: userData,
@@ -66,7 +66,7 @@ module.exports = function (SocketUser) {
}
const [list, uploaded] = await Promise.all([
- plugins.fireHook('filter:user.listPictures', {
+ plugins.hooks.fire('filter:user.listPictures', {
uid: data.uid,
pictures: [],
}),
diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js
index 463d509e71..c17f643ffd 100644
--- a/src/socket.io/user/profile.js
+++ b/src/socket.io/user/profile.js
@@ -46,7 +46,7 @@ module.exports = function (SocketUser) {
await user.isAdminOrGlobalModOrSelf(socket.uid, data.uid);
const userData = await user.getUserFields(data.uid, ['cover:url']);
await user.removeCoverPicture(data);
- plugins.fireHook('action:user.removeCoverPicture', {
+ plugins.hooks.fire('action:user.removeCoverPicture', {
callerUid: socket.uid,
uid: data.uid,
user: userData,
diff --git a/src/topics/create.js b/src/topics/create.js
index ca55a8066e..741447200f 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -39,7 +39,7 @@ module.exports = function (Topics) {
if (data.thumb) {
topicData.thumb = data.thumb;
}
- const result = await plugins.fireHook('filter:topic.create', { topic: topicData, data: data });
+ const result = await plugins.hooks.fire('filter:topic.create', { topic: topicData, data: data });
topicData = result.topic;
await db.setObject('topic:' + topicData.tid, topicData);
@@ -61,7 +61,7 @@ module.exports = function (Topics) {
Topics.createTags(data.tags, topicData.tid, timestamp),
]);
- plugins.fireHook('action:topic.save', { topic: _.clone(topicData), data: data });
+ plugins.hooks.fire('action:topic.save', { topic: _.clone(topicData), data: data });
return topicData.tid;
};
@@ -94,7 +94,7 @@ module.exports = function (Topics) {
if (!data.fromQueue) {
await user.isReadyToPost(data.uid, data.cid);
}
- const filteredData = await plugins.fireHook('filter:topic.post', data);
+ const filteredData = await plugins.hooks.fire('filter:topic.post', data);
data = filteredData;
const tid = await Topics.create(data);
@@ -124,7 +124,7 @@ module.exports = function (Topics) {
postData.index = 0;
analytics.increment(['topics', 'topics:byCid:' + topicData.cid]);
- plugins.fireHook('action:topic.post', { topic: topicData, post: postData, data: data });
+ plugins.hooks.fire('action:topic.post', { topic: topicData, post: postData, data: data });
if (parseInt(uid, 10)) {
user.notifications.sendTopicNotificationToFollowers(uid, topicData, postData);
@@ -168,7 +168,7 @@ module.exports = function (Topics) {
if (!data.fromQueue) {
await user.isReadyToPost(uid, data.cid);
}
- await plugins.fireHook('filter:topic.reply', data);
+ await plugins.hooks.fire('filter:topic.reply', data);
if (data.content) {
data.content = utils.rtrim(data.content);
}
@@ -197,7 +197,7 @@ module.exports = function (Topics) {
}
analytics.increment(['posts', 'posts:byCid:' + data.cid]);
- plugins.fireHook('action:topic.reply', { post: _.clone(postData), data: data });
+ plugins.hooks.fire('action:topic.reply', { post: _.clone(postData), data: data });
return postData;
};
diff --git a/src/topics/data.js b/src/topics/data.js
index 06476755ef..720d2e63ad 100644
--- a/src/topics/data.js
+++ b/src/topics/data.js
@@ -22,7 +22,7 @@ module.exports = function (Topics) {
}
const keys = tids.map(tid => 'topic:' + tid);
const topics = await (fields.length ? db.getObjectsFields(keys, fields) : db.getObjects(keys));
- const result = await plugins.fireHook('filter:topic.getFields', {
+ const result = await plugins.hooks.fire('filter:topic.getFields', {
tids: tids,
topics: topics,
fields: fields,
diff --git a/src/topics/delete.js b/src/topics/delete.js
index fce87776c2..5c3ec308be 100644
--- a/src/topics/delete.js
+++ b/src/topics/delete.js
@@ -100,7 +100,7 @@ module.exports = function (Topics) {
Topics.deleteTopicTags(tid),
reduceCounters(tid),
]);
- plugins.fireHook('action:topic.purge', { topic: deletedTopic, uid: uid });
+ plugins.hooks.fire('action:topic.purge', { topic: deletedTopic, uid: uid });
await db.delete('topic:' + tid);
};
diff --git a/src/topics/follow.js b/src/topics/follow.js
index d576578bc4..3790b7ca8c 100644
--- a/src/topics/follow.js
+++ b/src/topics/follow.js
@@ -44,7 +44,7 @@ module.exports = function (Topics) {
}
await method1(tid, uid);
await method2(tid, uid);
- plugins.fireHook(hook, { uid: uid, tid: tid });
+ plugins.hooks.fire(hook, { uid: uid, tid: tid });
}
async function follow(tid, uid) {
diff --git a/src/topics/fork.js b/src/topics/fork.js
index 15c784a926..3f5648221b 100644
--- a/src/topics/fork.js
+++ b/src/topics/fork.js
@@ -60,7 +60,7 @@ module.exports = function (Topics) {
db.sortedSetsAdd(['topics:votes', 'cid:' + cid + ':tids:votes'], postData.votes, tid),
]);
- plugins.fireHook('action:topic.fork', { tid: tid, fromTid: fromTid, uid: uid });
+ plugins.hooks.fire('action:topic.fork', { tid: tid, fromTid: fromTid, uid: uid });
return await Topics.getTopicData(tid);
};
@@ -93,7 +93,7 @@ module.exports = function (Topics) {
Topics.updateLastPostTimeFromLastPid(tid),
Topics.updateLastPostTimeFromLastPid(postData.tid),
]);
- plugins.fireHook('action:post.move', { uid: callerUid, post: postData, tid: tid });
+ plugins.hooks.fire('action:post.move', { uid: callerUid, post: postData, tid: tid });
};
async function updateCategory(postData, toTid) {
diff --git a/src/topics/index.js b/src/topics/index.js
index 372141dda5..99adfc9df2 100644
--- a/src/topics/index.js
+++ b/src/topics/index.js
@@ -132,7 +132,7 @@ Topics.getTopicsByTids = async function (tids, options) {
const filteredTopics = result.topics.filter(topic => topic && topic.category && !topic.category.disabled);
- const hookResult = await plugins.fireHook('filter:topics.get', { topics: filteredTopics, uid: uid });
+ const hookResult = await plugins.hooks.fire('filter:topics.get', { topics: filteredTopics, uid: uid });
return hookResult.topics;
};
@@ -152,7 +152,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
getMainPostAndReplies(topicData, set, uid, start, stop, reverse),
categories.getCategoryData(topicData.cid),
categories.getTagWhitelist([topicData.cid]),
- plugins.fireHook('filter:topic.thread_tools', { topic: topicData, uid: uid, tools: [] }),
+ plugins.hooks.fire('filter:topic.thread_tools', { topic: topicData, uid: uid, tools: [] }),
Topics.getFollowData([topicData.tid], uid),
Topics.getUserBookmark(topicData.tid, uid),
social.getActivePostSharing(),
@@ -184,7 +184,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
topicData.unreplied = topicData.postcount === 1;
topicData.icons = [];
- const result = await plugins.fireHook('filter:topic.get', { topic: topicData, uid: uid });
+ const result = await plugins.hooks.fire('filter:topic.get', { topic: topicData, uid: uid });
return result.topic;
};
@@ -282,7 +282,7 @@ Topics.isLocked = async function (tid) {
};
Topics.search = async function (tid, term) {
- const pids = await plugins.fireHook('filter:topic.search', {
+ const pids = await plugins.hooks.fire('filter:topic.search', {
tid: tid,
term: term,
});
diff --git a/src/topics/merge.js b/src/topics/merge.js
index dbcfebc5c2..b3f5718422 100644
--- a/src/topics/merge.js
+++ b/src/topics/merge.js
@@ -32,7 +32,7 @@ module.exports = function (Topics) {
});
});
- plugins.fireHook('action:topic.merge', { uid: uid, tids: tids, mergeIntoTid: mergeIntoTid, otherTids: otherTids });
+ plugins.hooks.fire('action:topic.merge', { uid: uid, tids: tids, mergeIntoTid: mergeIntoTid, otherTids: otherTids });
return mergeIntoTid;
};
diff --git a/src/topics/posts.js b/src/topics/posts.js
index a7a9189233..ae631be1ae 100644
--- a/src/topics/posts.js
+++ b/src/topics/posts.js
@@ -28,7 +28,7 @@ module.exports = function (Topics) {
if (!Array.isArray(postData) || !postData.length) {
return [];
}
- var pids = postData.map(post => post && post.pid);
+ const pids = postData.map(post => post && post.pid);
async function getPostUserData(field, method) {
const uids = _.uniq(postData.filter(p => p && parseInt(p[field], 10) >= 0).map(p => p[field]));
@@ -72,7 +72,7 @@ module.exports = function (Topics) {
}
});
- const result = await plugins.fireHook('filter:topics.addPostData', {
+ const result = await plugins.hooks.fire('filter:topics.addPostData', {
posts: postData,
uid: uid,
});
@@ -80,9 +80,10 @@ module.exports = function (Topics) {
};
Topics.modifyPostsByPrivilege = function (topicData, topicPrivileges) {
- var loggedIn = parseInt(topicPrivileges.uid, 10) > 0;
+ const loggedIn = parseInt(topicPrivileges.uid, 10) > 0;
topicData.posts.forEach(function (post) {
if (post) {
+ post.topicOwnerPost = parseInt(topicData.uid, 10) === parseInt(post.uid, 10);
post.display_edit_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:edit']);
post.display_delete_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:delete']);
post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools;
@@ -245,7 +246,7 @@ module.exports = function (Topics) {
const uniquePids = _.uniq(_.flatten(arrayOfReplyPids));
let replyData = await posts.getPostsFields(uniquePids, ['pid', 'uid', 'timestamp']);
- const result = await plugins.fireHook('filter:topics.getPostReplies', {
+ const result = await plugins.hooks.fire('filter:topics.getPostReplies', {
uid: callerUid,
replies: replyData,
});
diff --git a/src/topics/recent.js b/src/topics/recent.js
index 2a7f54fecd..0cc8ec3630 100644
--- a/src/topics/recent.js
+++ b/src/topics/recent.js
@@ -69,8 +69,8 @@ module.exports = function (Topics) {
Topics.updateRecent = async function (tid, timestamp) {
let data = { tid: tid, timestamp: timestamp };
- if (plugins.hasListeners('filter:topics.updateRecent')) {
- data = await plugins.fireHook('filter:topics.updateRecent', { tid: tid, timestamp: timestamp });
+ if (plugins.hooks.hasListeners('filter:topics.updateRecent')) {
+ data = await plugins.hooks.fire('filter:topics.updateRecent', { tid: tid, timestamp: timestamp });
}
if (data && data.tid && data.timestamp) {
await db.sortedSetAdd('topics:recent', data.timestamp, data.tid);
diff --git a/src/topics/sorted.js b/src/topics/sorted.js
index 28844ffee7..a8d4ac53b7 100644
--- a/src/topics/sorted.js
+++ b/src/topics/sorted.js
@@ -34,8 +34,8 @@ module.exports = function (Topics) {
};
async function getTids(params) {
- if (plugins.hasListeners('filter:topics.getSortedTids')) {
- const result = await plugins.fireHook('filter:topics.getSortedTids', { params: params, tids: [] });
+ if (plugins.hooks.hasListeners('filter:topics.getSortedTids')) {
+ const result = await plugins.hooks.fire('filter:topics.getSortedTids', { params: params, tids: [] });
return result.tids;
}
let tids = [];
@@ -66,10 +66,9 @@ module.exports = function (Topics) {
}
pinnedSets.push('cid:' + cid + ':tids:pinned');
});
- const [tids, pinnedTids] = await Promise.all([
- db.getSortedSetRevRange(sets, 0, meta.config.recentMaxTopics - 1),
- db.getSortedSetRevRange(pinnedSets, 0, -1),
- ]);
+ let pinnedTids = await db.getSortedSetRevRange(pinnedSets, 0, -1);
+ pinnedTids = await Topics.tools.checkPinExpiry(pinnedTids);
+ const tids = await db.getSortedSetRevRange(sets, 0, meta.config.recentMaxTopics - 1);
return pinnedTids.concat(tids);
}
@@ -154,7 +153,7 @@ module.exports = function (Topics) {
const cids = params.cids && params.cids.map(String);
tids = topicData.filter(t => t && t.cid && !isCidIgnored[t.cid] && (!cids || cids.includes(String(t.cid)))).map(t => t.tid);
- const result = await plugins.fireHook('filter:topics.filterSortedTids', { tids: tids, params: params });
+ const result = await plugins.hooks.fire('filter:topics.filterSortedTids', { tids: tids, params: params });
return result.tids;
}
diff --git a/src/topics/tags.js b/src/topics/tags.js
index 4f544d09c5..967322da64 100644
--- a/src/topics/tags.js
+++ b/src/topics/tags.js
@@ -18,7 +18,7 @@ module.exports = function (Topics) {
if (!Array.isArray(tags) || !tags.length) {
return;
}
- const result = await plugins.fireHook('filter:tags.filter', { tags: tags, tid: tid });
+ const result = await plugins.hooks.fire('filter:tags.filter', { tags: tags, tid: tid });
tags = _.uniq(result.tags)
.map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength))
.filter(tag => tag && tag.length >= (meta.config.minimumTagLength || 3));
@@ -110,13 +110,13 @@ module.exports = function (Topics) {
Topics.getTagTids = async function (tag, start, stop) {
const tids = await db.getSortedSetRevRange('tag:' + tag + ':topics', start, stop);
- const payload = await plugins.fireHook('filter:topics.getTagTids', { tag, start, stop, tids });
+ const payload = await plugins.hooks.fire('filter:topics.getTagTids', { tag, start, stop, tids });
return payload.tids;
};
Topics.getTagTopicCount = async function (tag) {
const count = await db.sortedSetCard('tag:' + tag + ':topics');
- const payload = await plugins.fireHook('filter:topics.getTagTopicCount', { tag, count });
+ const payload = await plugins.hooks.fire('filter:topics.getTagTopicCount', { tag, count });
return payload.count;
};
@@ -149,7 +149,7 @@ module.exports = function (Topics) {
Topics.getTags = async function (start, stop) {
const tags = await db.getSortedSetRevRangeWithScores('tags:topic:count', start, stop);
- const payload = await plugins.fireHook('filter:tags.getAll', {
+ const payload = await plugins.hooks.fire('filter:tags.getAll', {
tags: tags,
});
return await Topics.getTagData(payload.tags);
@@ -256,12 +256,12 @@ module.exports = function (Topics) {
return [];
}
let result;
- if (plugins.hasListeners('filter:topics.searchTags')) {
- result = await plugins.fireHook('filter:topics.searchTags', { data: data });
+ if (plugins.hooks.hasListeners('filter:topics.searchTags')) {
+ result = await plugins.hooks.fire('filter:topics.searchTags', { data: data });
} else {
result = await findMatches(data.query, 0);
}
- result = await plugins.fireHook('filter:tags.search', { data: data, matches: result.matches });
+ result = await plugins.hooks.fire('filter:tags.search', { data: data, matches: result.matches });
return result.matches;
};
@@ -270,8 +270,8 @@ module.exports = function (Topics) {
return [];
}
let result;
- if (plugins.hasListeners('filter:topics.autocompleteTags')) {
- result = await plugins.fireHook('filter:topics.autocompleteTags', { data: data });
+ if (plugins.hooks.hasListeners('filter:topics.autocompleteTags')) {
+ result = await plugins.hooks.fire('filter:topics.autocompleteTags', { data: data });
} else {
result = await findMatches(data.query, data.cid);
}
@@ -342,8 +342,8 @@ module.exports = function (Topics) {
};
Topics.getRelatedTopics = async function (topicData, uid) {
- if (plugins.hasListeners('filter:topic.getRelatedTopics')) {
- const result = await plugins.fireHook('filter:topic.getRelatedTopics', { topic: topicData, uid: uid, topics: [] });
+ if (plugins.hooks.hasListeners('filter:topic.getRelatedTopics')) {
+ const result = await plugins.hooks.fire('filter:topic.getRelatedTopics', { topic: topicData, uid: uid, topics: [] });
return result.topics;
}
diff --git a/src/topics/teaser.js b/src/topics/teaser.js
index a9127bcd0d..8bf4906907 100644
--- a/src/topics/teaser.js
+++ b/src/topics/teaser.js
@@ -67,7 +67,7 @@ module.exports = function (Topics) {
});
await Promise.all(postData.map(p => posts.parsePost(p)));
- const { tags } = await plugins.fireHook('filter:teasers.configureStripTags', { tags: utils.stripTags.concat(['img']) });
+ const { tags } = await plugins.hooks.fire('filter:teasers.configureStripTags', { tags: utils.stripTags.concat(['img']) });
var teasers = topics.map(function (topic, index) {
if (!topic) {
@@ -82,7 +82,7 @@ module.exports = function (Topics) {
return tidToPost[topic.tid];
});
- const result = await plugins.fireHook('filter:teasers.get', { teasers: teasers, uid: uid });
+ const result = await plugins.hooks.fire('filter:teasers.get', { teasers: teasers, uid: uid });
return result.teasers;
};
diff --git a/src/topics/thumb.js b/src/topics/thumb.js
index 356be5b4a3..9afd2ddb6b 100644
--- a/src/topics/thumb.js
+++ b/src/topics/thumb.js
@@ -23,6 +23,14 @@ module.exports = function (Topics) {
const pipeToFileAsync = util.promisify(pipeToFile);
Topics.resizeAndUploadThumb = async function (data) {
+ const allowedExtensions = file.allowedExtensions();
+
+ // Handle protocol-relative URLs
+ if (data.thumb && data.thumb.startsWith('//')) {
+ data.thumb = `${nconf.get('secure') ? 'https' : 'http'}:${data.thumb}`;
+ }
+
+ // Only continue if passed in thumbnail exists and is a URL. A system path means an upload is not necessary.
if (!data.thumb || !validator.isURL(data.thumb)) {
return;
}
@@ -39,6 +47,11 @@ module.exports = function (Topics) {
if (!extension) {
extension = '.' + mime.getExtension(type);
}
+
+ if (!allowedExtensions.includes(extension)) {
+ throw new Error('[[error:invalid-file]]');
+ }
+
const filename = Date.now() + '-topic-thumb' + extension;
const folder = 'files';
pathToUpload = path.join(nconf.get('upload_path'), folder, filename);
@@ -47,18 +60,19 @@ module.exports = function (Topics) {
await image.isFileTypeAllowed(pathToUpload);
+ await image.checkDimensions(pathToUpload);
await image.resizeImage({
path: pathToUpload,
width: meta.config.topicThumbSize,
height: meta.config.topicThumbSize,
});
- if (!plugins.hasListeners('filter:uploadImage')) {
+ if (!plugins.hooks.hasListeners('filter:uploadImage')) {
data.thumb = '/assets/uploads/' + folder + '/' + filename;
return;
}
- const uploadedFile = await plugins.fireHook('filter:uploadImage', {
+ const uploadedFile = await plugins.hooks.fire('filter:uploadImage', {
image: { path: pathToUpload, name: '' },
uid: data.uid,
folder: folder,
diff --git a/src/topics/tools.js b/src/topics/tools.js
index 269538599e..5ddad97c01 100644
--- a/src/topics/tools.js
+++ b/src/topics/tools.js
@@ -3,6 +3,7 @@
const _ = require('lodash');
const db = require('../database');
+const topics = require('.');
const categories = require('../categories');
const user = require('../user');
const plugins = require('../plugins');
@@ -28,7 +29,7 @@ module.exports = function (Topics) {
}
const canDelete = await privileges.topics.canDelete(tid, uid);
- const data = await plugins.fireHook(isDelete ? 'filter:topic.delete' : 'filter:topic.restore', { topicData: topicData, uid: uid, isDelete: isDelete, canDelete: canDelete, canRestore: canDelete });
+ const data = await plugins.hooks.fire(isDelete ? 'filter:topic.delete' : 'filter:topic.restore', { topicData: topicData, uid: uid, isDelete: isDelete, canDelete: canDelete, canRestore: canDelete });
if ((!data.canDelete && data.isDelete) || (!data.canRestore && !data.isDelete)) {
throw new Error('[[error:no-privileges]]');
@@ -47,9 +48,9 @@ module.exports = function (Topics) {
data.topicData.deleted = data.isDelete ? 1 : 0;
if (data.isDelete) {
- plugins.fireHook('action:topic.delete', { topic: data.topicData, uid: data.uid });
+ plugins.hooks.fire('action:topic.delete', { topic: data.topicData, uid: data.uid });
} else {
- plugins.fireHook('action:topic.restore', { topic: data.topicData, uid: data.uid });
+ plugins.hooks.fire('action:topic.restore', { topic: data.topicData, uid: data.uid });
}
const userData = await user.getUserFields(data.uid, ['username', 'userslug']);
return {
@@ -96,7 +97,7 @@ module.exports = function (Topics) {
topicData.isLocked = lock; // deprecate in v2.0
topicData.locked = lock;
- plugins.fireHook('action:topic.lock', { topic: _.clone(topicData), uid: uid });
+ plugins.hooks.fire('action:topic.lock', { topic: _.clone(topicData), uid: uid });
return topicData;
}
@@ -108,13 +109,44 @@ module.exports = function (Topics) {
return await togglePin(tid, uid, false);
};
+ topicTools.setPinExpiry = async (tid, expiry, uid) => {
+ if (isNaN(parseInt(expiry, 10)) || expiry <= Date.now()) {
+ throw new Error('[[error:invalid-data]]');
+ }
+
+ const topicData = await Topics.getTopicFields(tid, ['tid', 'uid', 'cid']);
+ const isAdminOrMod = await privileges.categories.isAdminOrMod(topicData.cid, uid);
+ if (!isAdminOrMod) {
+ throw new Error('[[error:no-privileges]]');
+ }
+
+ await Topics.setTopicField(tid, 'pinExpiry', expiry);
+ plugins.hooks.fire('action:topic.setPinExpiry', { topic: _.clone(topicData), uid: uid });
+ };
+
+ topicTools.checkPinExpiry = async (tids) => {
+ const expiry = (await topics.getTopicsFields(tids, ['pinExpiry'])).map(obj => obj.pinExpiry);
+ const now = Date.now();
+
+ tids = await Promise.all(tids.map(async (tid, idx) => {
+ if (expiry[idx] && parseInt(expiry[idx], 10) <= now) {
+ await togglePin(tid, 'system', false);
+ return null;
+ }
+
+ return tid;
+ }));
+
+ return tids.filter(Boolean);
+ };
+
async function togglePin(tid, uid, pin) {
const topicData = await Topics.getTopicData(tid);
if (!topicData) {
throw new Error('[[error:no-topic]]');
}
- const isAdminOrMod = await privileges.categories.isAdminOrMod(topicData.cid, uid);
- if (!isAdminOrMod) {
+
+ if (uid !== 'system' && !await privileges.topics.can('moderate', tid, uid)) {
throw new Error('[[error:no-privileges]]');
}
@@ -130,6 +162,7 @@ module.exports = function (Topics) {
], tid));
} else {
promises.push(db.sortedSetRemove('cid:' + topicData.cid + ':tids:pinned', tid));
+ promises.push(Topics.deleteTopicField(tid, 'pinExpiry'));
promises.push(db.sortedSetAddBulk([
['cid:' + topicData.cid + ':tids', topicData.lastposttime, tid],
['cid:' + topicData.cid + ':tids:posts', topicData.postcount, tid],
@@ -142,7 +175,7 @@ module.exports = function (Topics) {
topicData.isPinned = pin; // deprecate in v2.0
topicData.pinned = pin;
- plugins.fireHook('action:topic.pin', { topic: _.clone(topicData), uid: uid });
+ plugins.hooks.fire('action:topic.pin', { topic: _.clone(topicData), uid });
return topicData;
}
@@ -222,6 +255,6 @@ module.exports = function (Topics) {
hookData.toCid = cid;
hookData.tid = tid;
- plugins.fireHook('action:topic.move', hookData);
+ plugins.hooks.fire('action:topic.move', hookData);
};
};
diff --git a/src/topics/unread.js b/src/topics/unread.js
index 084eea8a74..b274c5287f 100644
--- a/src/topics/unread.js
+++ b/src/topics/unread.js
@@ -48,7 +48,7 @@ module.exports = function (Topics) {
Topics.unreadCutoff = async function (uid) {
const cutoff = Date.now() - (meta.config.unreadCutoff * 86400000);
- const data = await plugins.fireHook('filter:topics.unreadCutoff', { uid: uid, cutoff: cutoff });
+ const data = await plugins.hooks.fire('filter:topics.unreadCutoff', { uid: uid, cutoff: cutoff });
return parseInt(data.cutoff, 10);
};
@@ -71,7 +71,7 @@ module.exports = function (Topics) {
return data;
}
- const result = await plugins.fireHook('filter:topics.getUnreadTids', {
+ const result = await plugins.hooks.fire('filter:topics.getUnreadTids', {
uid: uid,
tids: data.tids,
counts: data.counts,
@@ -176,8 +176,8 @@ module.exports = function (Topics) {
}
async function getCategoryTids(params) {
- if (plugins.hasListeners('filter:topics.unread.getCategoryTids')) {
- const result = await plugins.fireHook('filter:topics.unread.getCategoryTids', { params: params, tids: [] });
+ if (plugins.hooks.hasListeners('filter:topics.unread.getCategoryTids')) {
+ const result = await plugins.hooks.fire('filter:topics.unread.getCategoryTids', { params: params, tids: [] });
return result.tids;
}
if (params.filter === 'watched') {
@@ -297,7 +297,7 @@ module.exports = function (Topics) {
const cids = _.uniq(topicData.map(t => t && t.cid).filter(Boolean));
await categories.markAsRead(cids, uid);
- plugins.fireHook('action:topics.markAsRead', { uid: uid, tids: tids });
+ plugins.hooks.fire('action:topics.markAsRead', { uid: uid, tids: tids });
return true;
};
diff --git a/src/user/admin.js b/src/user/admin.js
index e48a86e676..981fd921b4 100644
--- a/src/user/admin.js
+++ b/src/user/admin.js
@@ -31,7 +31,7 @@ module.exports = function (User) {
User.getUsersCSV = async function () {
winston.verbose('[user/getUsersCSV] Compiling User CSV data');
- const data = await plugins.fireHook('filter:user.csvFields', { fields: ['uid', 'email', 'username'] });
+ const data = await plugins.hooks.fire('filter:user.csvFields', { fields: ['uid', 'email', 'username'] });
let csvContent = data.fields.join(',') + '\n';
await batch.processSortedSet('users:joindate', async (uids) => {
const usersData = await User.getUsersFields(uids, data.fields);
diff --git a/src/user/approval.js b/src/user/approval.js
index 0a2c200d7c..8ea5916610 100644
--- a/src/user/approval.js
+++ b/src/user/approval.js
@@ -28,7 +28,7 @@ module.exports = function (User) {
ip: userData.ip,
hashedPassword: hashedPassword,
};
- const results = await plugins.fireHook('filter:user.addToApprovalQueue', { data: data, userData: userData });
+ const results = await plugins.hooks.fire('filter:user.addToApprovalQueue', { data: data, userData: userData });
await db.setObject('registration:queue:name:' + userData.username, results.data);
await db.sortedSetAdd('registration:queue', Date.now(), userData.username);
await sendNotificationToAdmins(userData.username);
@@ -69,7 +69,7 @@ module.exports = function (User) {
await User.setUserField(uid, 'password', userData.hashedPassword);
await removeFromQueue(username);
await markNotificationRead(username);
- await plugins.fireHook('filter:register.complete', { uid: uid });
+ await plugins.hooks.fire('filter:register.complete', { uid: uid });
await emailer.send('registration_accepted', uid, {
username: username,
subject: '[[email:welcome-to, ' + (meta.config.title || meta.config.browserTitle || 'NodeBB') + ']]',
@@ -140,7 +140,7 @@ module.exports = function (User) {
*/
}));
- const results = await plugins.fireHook('filter:user.getRegistrationQueue', { users: users });
+ const results = await plugins.hooks.fire('filter:user.getRegistrationQueue', { users: users });
return results.users;
};
diff --git a/src/user/blocks.js b/src/user/blocks.js
index 3384aa6b7b..494b440af7 100644
--- a/src/user/blocks.js
+++ b/src/user/blocks.js
@@ -64,7 +64,7 @@ module.exports = function (User) {
await User.incrementUserFieldBy(uid, 'blocksCount', 1);
User.blocks._cache.del(parseInt(uid, 10));
pubsub.publish('user:blocks:cache:del', parseInt(uid, 10));
- plugins.fireHook('action:user.blocks.add', { uid: uid, targetUid: targetUid });
+ plugins.hooks.fire('action:user.blocks.add', { uid: uid, targetUid: targetUid });
};
User.blocks.remove = async function (targetUid, uid) {
@@ -73,7 +73,7 @@ module.exports = function (User) {
await User.decrementUserFieldBy(uid, 'blocksCount', 1);
User.blocks._cache.del(parseInt(uid, 10));
pubsub.publish('user:blocks:cache:del', parseInt(uid, 10));
- plugins.fireHook('action:user.blocks.remove', { uid: uid, targetUid: targetUid });
+ plugins.hooks.fire('action:user.blocks.remove', { uid: uid, targetUid: targetUid });
};
User.blocks.applyChecks = async function (type, targetUid, uid) {
@@ -111,7 +111,7 @@ module.exports = function (User) {
set = set.filter(function (item) {
return !blockedSet.has(parseInt(isPlain ? item : (item && item[property]), 10));
});
- const data = await plugins.fireHook('filter:user.blocks.filter', { set: set, property: property, uid: uid, blockedSet: blockedSet });
+ const data = await plugins.hooks.fire('filter:user.blocks.filter', { set: set, property: property, uid: uid, blockedSet: blockedSet });
return data.set;
};
diff --git a/src/user/create.js b/src/user/create.js
index 5fb1c56ad8..51e7970e1c 100644
--- a/src/user/create.js
+++ b/src/user/create.js
@@ -67,7 +67,7 @@ module.exports = function (User) {
userData.userslug = slugify(renamedUsername);
}
- const results = await plugins.fireHook('filter:user.create', { user: userData, data: data });
+ const results = await plugins.hooks.fire('filter:user.create', { user: userData, data: data });
userData = results.user;
const uid = await db.incrObjectField('global', 'nextUid');
@@ -118,7 +118,7 @@ module.exports = function (User) {
if (userNameChanged) {
await User.notifications.sendNameChangeNotification(userData.uid, userData.username);
}
- plugins.fireHook('action:user.create', { user: userData, data: data });
+ plugins.hooks.fire('action:user.create', { user: userData, data: data });
return userData.uid;
}
diff --git a/src/user/data.js b/src/user/data.js
index c1fb6be11f..1fd101a8a9 100644
--- a/src/user/data.js
+++ b/src/user/data.js
@@ -60,7 +60,7 @@ module.exports = function (User) {
const uniqueUids = _.uniq(uids).filter(uid => uid > 0);
- const results = await plugins.fireHook('filter:user.whitelistFields', { uids: uids, whitelist: fieldWhitelist.slice() });
+ const results = await plugins.hooks.fire('filter:user.whitelistFields', { uids: uids, whitelist: fieldWhitelist.slice() });
if (!fields.length) {
fields = results.whitelist;
} else {
@@ -69,7 +69,7 @@ module.exports = function (User) {
}
let users = await db.getObjectsFields(uniqueUids.map(uid => 'user:' + uid), fields);
- const result = await plugins.fireHook('filter:user.getFields', {
+ const result = await plugins.hooks.fire('filter:user.getFields', {
uids: uniqueUids,
users: users,
fields: fields,
@@ -101,6 +101,10 @@ module.exports = function (User) {
if (fields.includes('banned') && !fields.includes('banned:expire')) {
addField('banned:expire');
}
+
+ if (fields.includes('username') && !fields.includes('fullname')) {
+ addField('fullname');
+ }
}
function uidsToUsers(uids, uniqueUids, usersData) {
@@ -136,14 +140,29 @@ module.exports = function (User) {
};
async function modifyUserData(users, requestedFields, fieldsToRemove) {
- users = await Promise.all(users.map(async function (user) {
+ let uidToSettings = {};
+ if (meta.config.showFullnameAsDisplayName) {
+ const uids = _.uniq(users.map(user => user.uid));
+ uidToSettings = _.zipObject(uids, await db.getObjectsFields(
+ uids.map(uid => 'user:' + uid + ':settings'),
+ ['showfullname']
+ ));
+ }
+ const uidToUser = {};
+ users.forEach(function (user) {
+ uidToUser[user.uid] = user;
+ });
+
+ await Promise.all(Object.keys(uidToUser).map(async function (uid) {
+ const user = uidToUser[uid];
if (!user) {
- return user;
+ return;
}
db.parseIntFields(user, intFields, requestedFields);
if (user.hasOwnProperty('username')) {
+ parseDisplayName(user, uidToSettings);
user.username = validator.escape(user.username ? user.username.toString() : '');
}
@@ -154,6 +173,7 @@ module.exports = function (User) {
if (!parseInt(user.uid, 10)) {
user.uid = 0;
user.username = (user.hasOwnProperty('oldUid') && parseInt(user.oldUid, 10)) ? '[[global:former_user]]' : '[[global:guest]]';
+ user.displayname = user.username;
user.userslug = '';
user.picture = User.getDefaultAvatar();
user['icon:text'] = '?';
@@ -209,10 +229,26 @@ module.exports = function (User) {
user.banned = false;
}
}
- return user;
}));
- return await plugins.fireHook('filter:users.get', users);
+ return await plugins.hooks.fire('filter:users.get', users);
+ }
+
+ function parseDisplayName(user, uidToSettings) {
+ let showfullname = parseInt(meta.config.showfullname, 10) === 1;
+ if (uidToSettings[user.uid]) {
+ if (parseInt(uidToSettings[user.uid].showfullname, 10) === 0) {
+ showfullname = false;
+ } else if (parseInt(uidToSettings[user.uid].showfullname, 10) === 1) {
+ showfullname = true;
+ }
+ }
+
+ user.displayname = validator.escape(String(
+ meta.config.showFullnameAsDisplayName && showfullname && user.fullname ?
+ user.fullname :
+ user.username
+ ));
}
function parseGroupTitle(user) {
@@ -253,7 +289,7 @@ module.exports = function (User) {
await db.setObject('user:' + uid, data);
for (const field in data) {
if (data.hasOwnProperty(field)) {
- plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' });
+ plugins.hooks.fire('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' });
}
}
};
@@ -268,7 +304,7 @@ module.exports = function (User) {
async function incrDecrUserFieldBy(uid, field, value, type) {
const newValue = await db.incrObjectFieldBy('user:' + uid, field, value);
- plugins.fireHook('action:user.set', { uid: uid, field: field, value: newValue, type: type });
+ plugins.hooks.fire('action:user.set', { uid: uid, field: field, value: newValue, type: type });
return newValue;
}
};
diff --git a/src/user/delete.js b/src/user/delete.js
index ee58a72026..d5ca3ccc4b 100644
--- a/src/user/delete.js
+++ b/src/user/delete.js
@@ -102,7 +102,7 @@ module.exports = function (User) {
throw new Error('[[error:no-user]]');
}
- await plugins.fireHook('static:user.delete', { uid: uid });
+ await plugins.hooks.fire('static:user.delete', { uid: uid });
await deleteVotes(uid);
await deleteChats(uid);
await User.auth.revokeAllSessions(uid);
@@ -151,6 +151,7 @@ module.exports = function (User) {
deleteImages(uid),
groups.leaveAllGroups(uid),
flags.resolveFlag('user', uid, uid),
+ User.reset.cleanByUid(uid),
]);
await db.deleteAll(['followers:' + uid, 'following:' + uid, 'user:' + uid]);
delete deletesInProgress[uid];
diff --git a/src/user/digest.js b/src/user/digest.js
index f931a183de..689d0ff771 100644
--- a/src/user/digest.js
+++ b/src/user/digest.js
@@ -87,7 +87,7 @@ Digest.getSubscribers = async function (interval) {
batch: 500,
});
- const results = await plugins.fireHook('filter:digest.subscribers', {
+ const results = await plugins.hooks.fire('filter:digest.subscribers', {
interval: interval,
subscribers: subscribers,
});
diff --git a/src/user/email.js b/src/user/email.js
index 9fab6c9ca2..92e05aa873 100644
--- a/src/user/email.js
+++ b/src/user/email.js
@@ -60,7 +60,7 @@ UserEmail.sendValidationEmail = async function (uid, options) {
}
await db.set('uid:' + uid + ':confirm:email:sent', 1);
await db.pexpireAt('uid:' + uid + ':confirm:email:sent', Date.now() + (emailInterval * 60 * 1000));
- confirm_code = await plugins.fireHook('filter:user.verify.code', confirm_code);
+ confirm_code = await plugins.hooks.fire('filter:user.verify.code', confirm_code);
await db.setObject('confirm:' + confirm_code, {
email: options.email.toLowerCase(),
@@ -79,8 +79,8 @@ UserEmail.sendValidationEmail = async function (uid, options) {
uid: uid,
};
- if (plugins.hasListeners('action:user.verify')) {
- plugins.fireHook('action:user.verify', { uid: uid, data: data });
+ if (plugins.hooks.hasListeners('action:user.verify')) {
+ plugins.hooks.fire('action:user.verify', { uid: uid, data: data });
} else {
await emailer.send(data.template, uid, data);
}
@@ -101,5 +101,5 @@ UserEmail.confirm = async function (code) {
await groups.leave('unverified-users', confirmObj.uid);
await db.delete('confirm:' + code);
await db.delete('uid:' + confirmObj.uid + ':confirm:email:sent');
- await plugins.fireHook('action:user.email.confirmed', { uid: confirmObj.uid, email: confirmObj.email });
+ await plugins.hooks.fire('action:user.email.confirmed', { uid: confirmObj.uid, email: confirmObj.email });
};
diff --git a/src/user/follow.js b/src/user/follow.js
index c8c281f927..8231f8300d 100644
--- a/src/user/follow.js
+++ b/src/user/follow.js
@@ -72,7 +72,7 @@ module.exports = function (User) {
return [];
}
const uids = await db.getSortedSetRevRange(type + ':' + uid, start, stop);
- const data = await plugins.fireHook('filter:user.' + type, {
+ const data = await plugins.hooks.fire('filter:user.' + type, {
uids: uids,
uid: uid,
start: start,
diff --git a/src/user/index.js b/src/user/index.js
index 81366a9b11..9ff484905f 100644
--- a/src/user/index.js
+++ b/src/user/index.js
@@ -64,10 +64,10 @@ User.getUsersFromSet = async function (set, uid, start, stop) {
};
User.getUsersWithFields = async function (uids, fields, uid) {
- let results = await plugins.fireHook('filter:users.addFields', { fields: fields });
+ let results = await plugins.hooks.fire('filter:users.addFields', { fields: fields });
results.fields = _.uniq(results.fields);
const userData = await User.getUsersFields(uids, results.fields);
- results = await plugins.fireHook('filter:userlist.get', { users: userData, uid: uid });
+ results = await plugins.hooks.fire('filter:userlist.get', { users: userData, uid: uid });
return results.users;
};
@@ -219,7 +219,7 @@ User.getModeratedCids = async function (uid) {
};
User.addInterstitials = function (callback) {
- plugins.registerHook('core', {
+ plugins.hooks.register('core', {
hook: 'filter:register.interstitial',
method: [
// GDPR information collection/processing consent + email consent
@@ -272,7 +272,7 @@ User.addInterstitials = function (callback) {
}
}
- const termsOfUse = await plugins.fireHook('filter:parse.post', {
+ const termsOfUse = await plugins.hooks.fire('filter:parse.post', {
postData: {
content: meta.config.termsOfUse || '',
},
diff --git a/src/user/jobs.js b/src/user/jobs.js
index 3ba4a2db13..d170bfec31 100644
--- a/src/user/jobs.js
+++ b/src/user/jobs.js
@@ -9,7 +9,7 @@ var jobs = {};
module.exports = function (User) {
User.startJobs = function () {
- winston.verbose('[user/jobs] (Re-)starting user jobs...');
+ winston.verbose('[user/jobs] (Re-)starting jobs...');
var started = 0;
var digestHour = meta.config.digestHour;
diff --git a/src/user/notifications.js b/src/user/notifications.js
index fe22127c5a..518d78a4c0 100644
--- a/src/user/notifications.js
+++ b/src/user/notifications.js
@@ -91,12 +91,12 @@ UserNotifications.getNotifications = async function (nids, uid) {
notification.readClass = !notification.read ? 'unread' : '';
}
- return notification && notification.path;
+ return notification;
});
await deleteUserNids(deletedNids, uid);
notificationData = await notifications.merge(notificationData);
- const result = await plugins.fireHook('filter:user.notifications.getNotifications', {
+ const result = await plugins.hooks.fire('filter:user.notifications.getNotifications', {
uid: uid,
notifications: notificationData,
});
diff --git a/src/user/online.js b/src/user/online.js
index 05c40464f7..a4c2be313c 100644
--- a/src/user/online.js
+++ b/src/user/online.js
@@ -23,7 +23,7 @@ module.exports = function (User) {
}
await db.sortedSetAdd('users:online', now, uid);
topics.pushUnreadCount(uid);
- plugins.fireHook('action:user.online', { uid: uid, timestamp: now });
+ plugins.hooks.fire('action:user.online', { uid: uid, timestamp: now });
};
User.isOnline = async function (uid) {
diff --git a/src/user/picture.js b/src/user/picture.js
index c7694a47d4..74642fe68b 100644
--- a/src/user/picture.js
+++ b/src/user/picture.js
@@ -16,7 +16,7 @@ module.exports = function (User) {
User.getAllowedImageTypes = function () {
const allowedTypes = ['image/png', 'image/jpeg', 'image/bmp'];
- if (plugins.hasListeners('filter:image.isFileTypeAllowed')) {
+ if (plugins.hooks.hasListeners('filter:image.isFileTypeAllowed')) {
allowedTypes.push('image/gif');
}
return allowedTypes;
diff --git a/src/user/profile.js b/src/user/profile.js
index e2ca5f03a0..2a571d4cfc 100644
--- a/src/user/profile.js
+++ b/src/user/profile.js
@@ -22,7 +22,7 @@ module.exports = function (User) {
}
const updateUid = data.uid;
- const result = await plugins.fireHook('filter:user.updateProfile', {
+ const result = await plugins.hooks.fire('filter:user.updateProfile', {
uid: uid,
data: data,
fields: fields,
@@ -52,7 +52,7 @@ module.exports = function (User) {
await User.setUserField(updateUid, field, data[field]);
});
- plugins.fireHook('action:user.updateProfile', {
+ plugins.hooks.fire('action:user.updateProfile', {
uid: uid,
data: data,
fields: fields,
@@ -332,6 +332,6 @@ module.exports = function (User) {
User.auth.revokeAllSessions(data.uid),
]);
- plugins.fireHook('action:password.change', { uid: uid, targetUid: data.uid });
+ plugins.hooks.fire('action:password.change', { uid: uid, targetUid: data.uid });
};
};
diff --git a/src/user/search.js b/src/user/search.js
index 32c9d6bd2f..c7f3c7897e 100644
--- a/src/user/search.js
+++ b/src/user/search.js
@@ -49,7 +49,7 @@ module.exports = function (User) {
}
uids = await filterAndSortUids(uids, data);
- const result = await plugins.fireHook('filter:users.search', { uids: uids, uid: uid });
+ const result = await plugins.hooks.fire('filter:users.search', { uids: uids, uid: uid });
uids = result.uids;
const searchResult = {
diff --git a/src/user/settings.js b/src/user/settings.js
index ebb153852f..d486299625 100644
--- a/src/user/settings.js
+++ b/src/user/settings.js
@@ -36,7 +36,7 @@ module.exports = function (User) {
};
async function onSettingsLoaded(uid, settings) {
- const data = await plugins.fireHook('filter:user.getSettings', { uid: uid, settings: settings });
+ const data = await plugins.hooks.fire('filter:user.getSettings', { uid: uid, settings: settings });
settings = data.settings;
const defaultTopicsPerPage = meta.config.topicsPerPage;
@@ -101,7 +101,7 @@ module.exports = function (User) {
}
data.userLang = data.userLang || meta.config.defaultLang;
- plugins.fireHook('action:user.saveSettings', { uid: uid, settings: data });
+ plugins.hooks.fire('action:user.saveSettings', { uid: uid, settings: data });
const settings = {
showemail: data.showemail,
@@ -132,7 +132,7 @@ module.exports = function (User) {
settings[notificationType] = data[notificationType];
}
});
- const result = await plugins.fireHook('filter:user.saveSettings', { settings: settings, data: data });
+ const result = await plugins.hooks.fire('filter:user.saveSettings', { settings: settings, data: data });
await db.setObject('user:' + uid + ':settings', result.settings);
await User.updateDigestSetting(uid, data.dailyDigestFreq);
return await User.getSettings(uid);
diff --git a/src/views/admin/development/info.tpl b/src/views/admin/development/info.tpl
index 5d4df86bd5..604e13bfb7 100644
--- a/src/views/admin/development/info.tpl
+++ b/src/views/admin/development/info.tpl
@@ -41,7 +41,7 @@
{info.process.cpuUsage.user} / {info.process.cpuUsage.system}
{info.process.memoryUsage.humanReadable} mb / {info.os.freemem} mb
{info.os.load}
- {info.process.uptime}
+ {info.process.uptimeHumanReadable}
diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl
index 282d864b06..ed37a7a8cc 100644
--- a/src/views/admin/manage/group.tpl
+++ b/src/views/admin/manage/group.tpl
@@ -17,7 +17,7 @@
diff --git a/src/views/admin/partials/privileges/global.tpl b/src/views/admin/partials/privileges/global.tpl
index ad36cdca16..2c704ea7b4 100644
--- a/src/views/admin/partials/privileges/global.tpl
+++ b/src/views/admin/partials/privileges/global.tpl
@@ -3,6 +3,7 @@
[[admin/manage/categories:privileges.section-group]]
+ Select/Clear All
{privileges.labels.groups.name}
@@ -18,6 +19,7 @@
{privileges.groups.name}
+
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
@@ -45,6 +47,7 @@
[[admin/manage/categories:privileges.section-user]]
+ Select/Clear All
{privileges.labels.users.name}
@@ -61,6 +64,7 @@
{privileges.users.username}
+
{function.spawnPrivilegeStates, privileges.users.username, ../privileges}
diff --git a/src/views/admin/settings/api.tpl b/src/views/admin/settings/api.tpl
index 783d53b6bd..0f40fe9f2d 100644
--- a/src/views/admin/settings/api.tpl
+++ b/src/views/admin/settings/api.tpl
@@ -1,18 +1,33 @@