diff --git a/CHANGELOG.md b/CHANGELOG.md
index de31b7fb97..466a49bd4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,47 @@
+#### v1.18.1 (2021-09-03)
+
+##### Chores
+
+* found some hooks that don't play well docgen (ae793b4a)
+* incrementing version number - v1.18.0 (1e436ae7)
+* update changelog for v1.18.0 (2fd9c095)
+* **deps:** update dependency mocha to v9.1.1 (64bac178)
+
+##### New Features
+
+* create folders in ACP uploads #9638 (#9750) (3df79683)
+* column based view on wide priv. tables (#9699) (61f02f17)
+* als (#9749) (e59d3575)
+* add quick reply key (e9314842)
+* add new lang key for no best posts (6e73d8c9)
+
+##### Bug Fixes
+
+* **deps:**
+ * update dependency autoprefixer to v10.3.4 (67b932f4)
+ * update dependency nodebb-theme-persona to v11.2.4 (fe18e100)
+ * update dependency mongodb to v3.7.0 (31a35d7f)
+ * update socket.io packages to v4.2.0 (f2028d70)
+ * update dependency ioredis to v4.27.9 (6052eb16)
+ * update dependency mongodb to v3.6.12 (#9761) (5fa982c1)
+ * update dependency nodebb-plugin-composer-default to v7.0.2 (33d51201)
+ * update dependency nodebb-theme-slick to v1.4.12 (1b416d7e)
+ * update dependency nodebb-theme-slick to v1.4.11 (65b32fa1)
+ * update dependency nodebb-theme-persona to v11.2.3 (6ce321e4)
+ * update dependency autoprefixer to v10.3.3 (91ba7cdf)
+ * update dependency nodebb-theme-slick to v1.4.9 (d80b378f)
+ * update dependency jquery-deserialize to v2.0.0 (#9744) (7f9451ce)
+* determine indeterminate checkboxes (760ea9df)
+* move app.alert back into the conditionals (ca9bae3a)
+* only show email confirmation warning toast on pages that it applies (1bd1cc74)
+* updated email confirm warning to be more positive (2d1380dd)
+* automated tests are a good thing to have (6afeac37)
+
+##### Refactors
+
+* consistent jquery element var naming (fc0e655e)
+* var to const (1272da65)
+
#### v1.18.0 (2021-08-25)
##### Breaking Changes
diff --git a/install/package.json b/install/package.json
index 345e5e1add..1cb2211274 100644
--- a/install/package.json
+++ b/install/package.json
@@ -85,7 +85,7 @@
"@nodebb/bootswatch": "3.4.2",
"nconf": "^0.11.2",
"nodebb-plugin-composer-default": "7.0.2",
- "nodebb-plugin-dbsearch": "5.0.2",
+ "nodebb-plugin-dbsearch": "5.0.3",
"nodebb-plugin-emoji": "^3.5.0",
"nodebb-plugin-emoji-android": "2.0.5",
"nodebb-plugin-markdown": "8.14.3",
@@ -93,9 +93,9 @@
"nodebb-plugin-spam-be-gone": "0.7.9",
"nodebb-rewards-essentials": "0.1.5",
"nodebb-theme-lavender": "5.2.1",
- "nodebb-theme-persona": "11.2.4",
+ "nodebb-theme-persona": "11.2.5",
"nodebb-theme-slick": "1.4.12",
- "nodebb-theme-vanilla": "12.1.2",
+ "nodebb-theme-vanilla": "12.1.3",
"nodebb-widget-essentials": "5.0.4",
"nodemailer": "^6.5.0",
"nprogress": "0.2.0",
@@ -116,7 +116,7 @@
"sanitize-html": "^2.3.2",
"semver": "^7.3.4",
"serve-favicon": "^2.5.0",
- "sharp": "0.29.0",
+ "sharp": "0.29.1",
"sitemap": "^7.0.0",
"slideout": "1.0.1",
"socket.io": "4.2.0",
@@ -142,8 +142,8 @@
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.0.3",
- "@commitlint/cli": "12.1.4",
- "@commitlint/config-angular": "12.1.4",
+ "@commitlint/cli": "13.1.0",
+ "@commitlint/config-angular": "13.1.0",
"coveralls": "3.1.1",
"eslint": "7.32.0",
"eslint-config-nodebb": "0.0.2",
diff --git a/public/language/ar/admin/extend/rewards.json b/public/language/ar/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/ar/admin/extend/rewards.json
+++ b/public/language/ar/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/bg/admin/extend/rewards.json b/public/language/bg/admin/extend/rewards.json
index 890cc63d9e..d8860e3193 100644
--- a/public/language/bg/admin/extend/rewards.json
+++ b/public/language/bg/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Изтриване",
"enable": "Включване",
"disable": "Изключване",
- "control-panel": "Управление на наградите",
- "new-reward": "Нова награда",
"alert.delete-success": "Наградата е изтрита успешно",
"alert.no-inputs-found": "Неправомерна награда — няма нищо въведено!",
diff --git a/public/language/bn/admin/extend/rewards.json b/public/language/bn/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/bn/admin/extend/rewards.json
+++ b/public/language/bn/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/cs/admin/extend/rewards.json b/public/language/cs/admin/extend/rewards.json
index dd95f17371..9f0d26cfc3 100644
--- a/public/language/cs/admin/extend/rewards.json
+++ b/public/language/cs/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Odstranit",
"enable": "Povolit",
"disable": "Zakázat",
- "control-panel": "Ovládací panel odměn",
- "new-reward": "Nová odměna",
"alert.delete-success": "Odměna byla úspěšně smazána",
"alert.no-inputs-found": "Nepovolená odměna – nebyl nalezen žádný záznam.",
diff --git a/public/language/da/admin/extend/rewards.json b/public/language/da/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/da/admin/extend/rewards.json
+++ b/public/language/da/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/de/admin/extend/rewards.json b/public/language/de/admin/extend/rewards.json
index 38c39fcf44..7a6eef6513 100644
--- a/public/language/de/admin/extend/rewards.json
+++ b/public/language/de/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Entfernen",
"enable": "Aktivieren",
"disable": "Deaktivieren",
- "control-panel": "Belohnungseinstellungen",
- "new-reward": "Neue Belohnung",
"alert.delete-success": "Die Belohnung wurde erfolgreich gelöscht",
"alert.no-inputs-found": "Ungültige Belohnung - keine Eingaben gefunden!",
diff --git a/public/language/el/admin/extend/rewards.json b/public/language/el/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/el/admin/extend/rewards.json
+++ b/public/language/el/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/en-GB/admin/extend/rewards.json b/public/language/en-GB/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/en-GB/admin/extend/rewards.json
+++ b/public/language/en-GB/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/en-US/admin/extend/rewards.json b/public/language/en-US/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/en-US/admin/extend/rewards.json
+++ b/public/language/en-US/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/en-x-pirate/admin/extend/rewards.json b/public/language/en-x-pirate/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/en-x-pirate/admin/extend/rewards.json
+++ b/public/language/en-x-pirate/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/es/admin/extend/rewards.json b/public/language/es/admin/extend/rewards.json
index 44eb3f7108..98ded7cfa7 100644
--- a/public/language/es/admin/extend/rewards.json
+++ b/public/language/es/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Eliminar",
"enable": "Habilitar",
"disable": "Deshabilitar",
- "control-panel": "Control de recompensas",
- "new-reward": "Nueva Recompensa",
"alert.delete-success": "Recompensa eliminada con éxito",
"alert.no-inputs-found": "¡Recompensa ilegal - no se encontraron inputs!",
diff --git a/public/language/et/admin/extend/rewards.json b/public/language/et/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/et/admin/extend/rewards.json
+++ b/public/language/et/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/fa-IR/admin/extend/rewards.json b/public/language/fa-IR/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/fa-IR/admin/extend/rewards.json
+++ b/public/language/fa-IR/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/fi/admin/extend/rewards.json b/public/language/fi/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/fi/admin/extend/rewards.json
+++ b/public/language/fi/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/fr/admin/extend/rewards.json b/public/language/fr/admin/extend/rewards.json
index b1db437fc0..9fb342ce60 100644
--- a/public/language/fr/admin/extend/rewards.json
+++ b/public/language/fr/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Supprimer",
"enable": "Activer",
"disable": "Désactiver",
- "control-panel": "Contrôle des récompenses",
- "new-reward": "Nouvelle récompense",
"alert.delete-success": "Récompense supprimée",
"alert.no-inputs-found": "Récompense invalide - aucune entrée trouvée !",
diff --git a/public/language/fr/admin/manage/uploads.json b/public/language/fr/admin/manage/uploads.json
index a0a8608e07..8f1e276a6f 100644
--- a/public/language/fr/admin/manage/uploads.json
+++ b/public/language/fr/admin/manage/uploads.json
@@ -6,6 +6,6 @@
"size/filecount": "Taille / nombre de fichiers",
"confirm-delete": "Voulez-vous vraiment supprimer ce fichier?",
"filecount": "%1 fichiers",
- "new-folder": "New Folder",
- "name-new-folder": "Enter a name for new the folder"
+ "new-folder": "Nouveau Dossier",
+ "name-new-folder": "Entrez un nom pour le nouveau dossier"
}
\ No newline at end of file
diff --git a/public/language/fr/error.json b/public/language/fr/error.json
index 4ee0be6ce4..a3c870ca55 100644
--- a/public/language/fr/error.json
+++ b/public/language/fr/error.json
@@ -25,14 +25,14 @@
"invalid-event": "Événement non valide: %1",
"local-login-disabled": "Le système de connexion local a été désactivé pour les comptes sans privilèges.",
"csrf-invalid": "Nous ne pouvons pas vous connectez, probablement car votre session a expiré. Merci de réessayer.",
- "invalid-path": "Invalid path",
- "folder-exists": "Folder exists",
+ "invalid-path": "Chemin invalide",
+ "folder-exists": "Le dossier existe",
"invalid-pagination-value": "Valeur de pagination invalide. Celle-ci doit être comprise entre %1 et %2.",
"username-taken": "Nom d’utilisateur déjà utilisé",
"email-taken": "Email déjà utilisé",
"email-nochange": "Le mail saisi est déjà enregistré.",
"email-invited": "Cet utilisateur a déjà été invité.",
- "email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
+ "email-not-confirmed": "La publication dans certaines catégories ou sujets sera activée après confirmation de e-mail, veuillez cliquer ici pour envoyer un e-mail de confirmation.",
"email-not-confirmed-chat": "Il ne vous est pas possible d'utiliser le chat tant que votre adresse email n'a pas été vérifiée. Veuillez cliquer ici pour confirmer votre adresse email.",
"email-not-confirmed-email-sent": "Votre email n'a pas encore été confirmé, veuillez vérifier votre boîte mail. Vous ne pourrez pas poster ou discuter avant que votre email ne soit confirmé.",
"no-email-to-confirm": "Votre compte n'a pas d'adresse mail définie. Un mail est nécessaire pour la récupération du compte. Veuillez cliquer ici pour entrer un courriel.",
diff --git a/public/language/gl/admin/extend/rewards.json b/public/language/gl/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/gl/admin/extend/rewards.json
+++ b/public/language/gl/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/he/admin/extend/rewards.json b/public/language/he/admin/extend/rewards.json
index 55adfcb6e2..12bc679568 100644
--- a/public/language/he/admin/extend/rewards.json
+++ b/public/language/he/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "מחק",
"enable": "הפעל",
"disable": "השבת",
- "control-panel": "בקרת תגמולים",
- "new-reward": "תגמול חדש",
"alert.delete-success": "תגמול נמחק בהצלחה",
"alert.no-inputs-found": "תגמול לא חוקי - לא נמצא מידע!",
diff --git a/public/language/hr/admin/extend/rewards.json b/public/language/hr/admin/extend/rewards.json
index 5e20c3872f..d8198466a2 100644
--- a/public/language/hr/admin/extend/rewards.json
+++ b/public/language/hr/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Obriši",
"enable": "Omogući",
"disable": "onemogući",
- "control-panel": "Kontrola nagrada",
- "new-reward": "Nova nagrada",
"alert.delete-success": "Uspješno obrisana nagrada",
"alert.no-inputs-found": "Ilegalna nagrada - nije pronađen unos!",
diff --git a/public/language/hu/admin/extend/rewards.json b/public/language/hu/admin/extend/rewards.json
index 1258e3a0c6..d5fe130fe8 100644
--- a/public/language/hu/admin/extend/rewards.json
+++ b/public/language/hu/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Törlés",
"enable": "Engedélyezés",
"disable": "Tiltás",
- "control-panel": "Jutalom vezérlés",
- "new-reward": "Új jutalom",
"alert.delete-success": "Jutalom sikeresen törölve",
"alert.no-inputs-found": "Helytelen jutalom - nem található bevitel!",
diff --git a/public/language/id/admin/extend/rewards.json b/public/language/id/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/id/admin/extend/rewards.json
+++ b/public/language/id/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/it/admin/extend/rewards.json b/public/language/it/admin/extend/rewards.json
index 1f1e6bc50f..7778999fbd 100644
--- a/public/language/it/admin/extend/rewards.json
+++ b/public/language/it/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Elimina",
"enable": "Abilita",
"disable": "Disabilita",
- "control-panel": "Controllo dei premi",
- "new-reward": "Nuovo premio",
"alert.delete-success": "Premi eliminati con successo",
"alert.no-inputs-found": "Premio illegale - immissioni non trovate!",
diff --git a/public/language/it/admin/manage/privileges.json b/public/language/it/admin/manage/privileges.json
index 032e6a844a..0da351c033 100644
--- a/public/language/it/admin/manage/privileges.json
+++ b/public/language/it/admin/manage/privileges.json
@@ -51,13 +51,13 @@
"alert.saved": "Modifiche ai privilegi salvate e applicate",
"alert.confirm-discard": "Sei sicuro di voler annullare le modifiche ai privilegi?",
"alert.discarded": "Modifiche ai privilegi ignorate",
- "alert.confirm-copyToAll": "Are you sure you wish to apply this set of %1 to all categories?",
- "alert.confirm-copyToAllGroup": "Are you sure you wish to apply this group's set of %1 to all categories?",
- "alert.confirm-copyToChildren": "Are you sure you wish to apply this set of %1 to all descendant (child) categories?",
- "alert.confirm-copyToChildrenGroup": "Are you sure you wish to apply this group's set of %1 to all descendant (child) categories?",
+ "alert.confirm-copyToAll": "Sei sicuro di voler applicare questa serie di %1 a tutte le categorie?",
+ "alert.confirm-copyToAllGroup": "Sei sicuro di voler applicare questa serie di %1 del gruppo a tutte le categorie?",
+ "alert.confirm-copyToChildren": "Sei sicuro di voler applicare questa serie di %1 a tutte le categorie discendenti (figli)?",
+ "alert.confirm-copyToChildrenGroup": "Sei sicuro di voler applicare questa serie di %1 del questo gruppo a tutte le categorie discendenti (figli)?",
"alert.no-undo": "Questa azione non può essere annullata.",
"alert.admin-warning": "Gli amministratori ottengono implicitamente tutti i privilegi",
- "alert.copyPrivilegesFrom-title": "Select a category to copy from",
- "alert.copyPrivilegesFrom-warning": "This will copy %1 from the selected category.",
- "alert.copyPrivilegesFromGroup-warning": "This will copy this group's set of %1 from the selected category."
+ "alert.copyPrivilegesFrom-title": "Seleziona una categoria da cui copiare",
+ "alert.copyPrivilegesFrom-warning": "Questo copierà 1% dalla categoria selezionata.",
+ "alert.copyPrivilegesFromGroup-warning": "Questo copierà la serie di %1 da questo gruppo dalla categoria selezionata."
}
\ No newline at end of file
diff --git a/public/language/it/admin/manage/uploads.json b/public/language/it/admin/manage/uploads.json
index 7d12c43dd0..5d992eec52 100644
--- a/public/language/it/admin/manage/uploads.json
+++ b/public/language/it/admin/manage/uploads.json
@@ -6,6 +6,6 @@
"size/filecount": "Dimensione / Numero file",
"confirm-delete": "Vuoi davvero cancellare questo file?",
"filecount": "%1 file",
- "new-folder": "New Folder",
- "name-new-folder": "Enter a name for new the folder"
+ "new-folder": "Nuova cartella",
+ "name-new-folder": "Inserisci un nome per la nuova cartella"
}
\ No newline at end of file
diff --git a/public/language/it/error.json b/public/language/it/error.json
index 166ee800fa..c4a2350d94 100644
--- a/public/language/it/error.json
+++ b/public/language/it/error.json
@@ -25,14 +25,14 @@
"invalid-event": "Evento non valido: %1",
"local-login-disabled": "Il sistema di accesso locale è stato disabilitato per gli account senza privilegi.",
"csrf-invalid": "Non siamo riusciti a farti accedere, probabilmente perché la sessione è scaduta. Per favore riprova.",
- "invalid-path": "Invalid path",
- "folder-exists": "Folder exists",
+ "invalid-path": "Percorso non valido",
+ "folder-exists": "La cartella esiste",
"invalid-pagination-value": "Valore di impaginazione non valido, deve essere almeno %1 ed al massimo %2",
"username-taken": "Nome utente già esistente",
"email-taken": "Email già esistente",
"email-nochange": "L'email inserita è la stessa dell'email già presente in archivio.",
"email-invited": "L'email è già stata invitata",
- "email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
+ "email-not-confirmed": "Sarai abilitato a postare in alcune categorie o discussioni una volta che la tua email sarà confermata, per favore clicca qui per inviare una email di conferma.",
"email-not-confirmed-chat": "Non puoi chattare finché non confermi la tua email, per favore clicca qui per confermare la tua email.",
"email-not-confirmed-email-sent": "La tua email non è stata ancora confermata, controlla la tua casella di posta per l'email di conferma. Non potrai pubblicare post o chattare fino a quando la tua email non sarà confermata.",
"no-email-to-confirm": "Il tuo account non ha un'email impostata. Un'email è necessaria per il recupero dell'account. Clicca qui per inserire un'email.",
diff --git a/public/language/ja/admin/extend/rewards.json b/public/language/ja/admin/extend/rewards.json
index 3023baeba3..72ef6500b8 100644
--- a/public/language/ja/admin/extend/rewards.json
+++ b/public/language/ja/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "削除",
"enable": "有効",
"disable": "無効",
- "control-panel": "報酬コントロール",
- "new-reward": "新しい報酬",
"alert.delete-success": "報酬を削除しました",
"alert.no-inputs-found": "違法報酬 - 入力が見つかりません!",
diff --git a/public/language/ko/admin/extend/rewards.json b/public/language/ko/admin/extend/rewards.json
index ac23d6e380..be8bd8cf85 100644
--- a/public/language/ko/admin/extend/rewards.json
+++ b/public/language/ko/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "삭제",
"enable": "활성화",
"disable": "비활성화",
- "control-panel": "보상 제어판",
- "new-reward": "새로운 보상",
"alert.delete-success": "성공적으로 보상을 삭제했습니다.",
"alert.no-inputs-found": "잘못된 보상 - 입력값이 없습니다!",
diff --git a/public/language/lt/admin/extend/rewards.json b/public/language/lt/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/lt/admin/extend/rewards.json
+++ b/public/language/lt/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/lv/admin/extend/rewards.json b/public/language/lv/admin/extend/rewards.json
index ae99c8e088..97cf227477 100644
--- a/public/language/lv/admin/extend/rewards.json
+++ b/public/language/lv/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Izdzēst",
"enable": "Iespējot",
"disable": "Atspējot",
- "control-panel": "Balvu vadības panelis",
- "new-reward": "Jauna balva",
"alert.delete-success": "Veiksmīgi izdzēsta balva",
"alert.no-inputs-found": "Nederīga balva - nav ievažu!",
diff --git a/public/language/ms/admin/extend/rewards.json b/public/language/ms/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/ms/admin/extend/rewards.json
+++ b/public/language/ms/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/nb/admin/extend/rewards.json b/public/language/nb/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/nb/admin/extend/rewards.json
+++ b/public/language/nb/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/nl/admin/extend/rewards.json b/public/language/nl/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/nl/admin/extend/rewards.json
+++ b/public/language/nl/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/pl/admin/extend/rewards.json b/public/language/pl/admin/extend/rewards.json
index 41694a7ed8..afe04e15af 100644
--- a/public/language/pl/admin/extend/rewards.json
+++ b/public/language/pl/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Usuń",
"enable": "Włącz",
"disable": "Wyłącz",
- "control-panel": "Ustawienia nagród",
- "new-reward": "Nowa nagroda",
"alert.delete-success": "Pomyślnie usunięto nagrodę",
"alert.no-inputs-found": "Niepoprawnie dodana nagroda ",
diff --git a/public/language/pt-BR/admin/extend/rewards.json b/public/language/pt-BR/admin/extend/rewards.json
index 4b9c21f169..08b1de8119 100644
--- a/public/language/pt-BR/admin/extend/rewards.json
+++ b/public/language/pt-BR/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Deletar",
"enable": "Ativar",
"disable": "Desativar",
- "control-panel": "Controle de Recompensas",
- "new-reward": "Nova Recompensa",
"alert.delete-success": "Recompensa excluída com sucesso",
"alert.no-inputs-found": "Recompensa ilegal - nenhuma entrada encontrada!",
diff --git a/public/language/pt-PT/admin/extend/rewards.json b/public/language/pt-PT/admin/extend/rewards.json
index 7fab3c6679..346eb49e2a 100644
--- a/public/language/pt-PT/admin/extend/rewards.json
+++ b/public/language/pt-PT/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Apagar",
"enable": "Ativar",
"disable": "Desativar",
- "control-panel": "Controlo de Recompensas",
- "new-reward": "Nova Recompensa",
"alert.delete-success": "Recompensa apagada com sucesso",
"alert.no-inputs-found": "Recompensa ilegal - não foram encontradas entradas!",
diff --git a/public/language/ro/admin/extend/rewards.json b/public/language/ro/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/ro/admin/extend/rewards.json
+++ b/public/language/ro/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/ru/admin/extend/rewards.json b/public/language/ru/admin/extend/rewards.json
index 616afc7178..d6cc9c4623 100644
--- a/public/language/ru/admin/extend/rewards.json
+++ b/public/language/ru/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Удалить",
"enable": "Включить",
"disable": "Выключить",
- "control-panel": "Управление наградами",
- "new-reward": "Новая награда",
"alert.delete-success": "Награда успешно удалена",
"alert.no-inputs-found": "Некорректная награда!",
diff --git a/public/language/rw/admin/extend/rewards.json b/public/language/rw/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/rw/admin/extend/rewards.json
+++ b/public/language/rw/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/sc/admin/extend/rewards.json b/public/language/sc/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/sc/admin/extend/rewards.json
+++ b/public/language/sc/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/sk/admin/extend/rewards.json b/public/language/sk/admin/extend/rewards.json
index e5a4a84acd..87a475ac61 100644
--- a/public/language/sk/admin/extend/rewards.json
+++ b/public/language/sk/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Odstrániť",
"enable": "Povoliť",
"disable": "Zakázať",
- "control-panel": "Kontrola odmien",
- "new-reward": "Nová odmena",
"alert.delete-success": "Odmena bola úspešne vymazaná",
"alert.no-inputs-found": "Nepovolená odmena - nebol nájdený žiadny záznam.",
diff --git a/public/language/sl/admin/admin.json b/public/language/sl/admin/admin.json
index 36f0fdde60..3afbbf5ab7 100644
--- a/public/language/sl/admin/admin.json
+++ b/public/language/sl/admin/admin.json
@@ -1,7 +1,7 @@
{
- "alert.confirm-rebuild-and-restart": "Are you sure you wish to rebuild and restart NodeBB?",
- "alert.confirm-restart": "Are you sure you wish to restart NodeBB?",
+ "alert.confirm-rebuild-and-restart": "Ste prepričani, da želite znova zagnati NodeBB?",
+ "alert.confirm-restart": "Ste prepričani, da želite znova zagnati NodeBB?",
- "acp-title": "%1 | NodeBB Admin Control Panel",
+ "acp-title": "NodeBB administracijska nadzorna plošča",
"settings-header-contents": "Contents"
}
\ No newline at end of file
diff --git a/public/language/sl/admin/extend/rewards.json b/public/language/sl/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/sl/admin/extend/rewards.json
+++ b/public/language/sl/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/sl/category.json b/public/language/sl/category.json
index 6a7fda2a16..e96e8805f9 100644
--- a/public/language/sl/category.json
+++ b/public/language/sl/category.json
@@ -10,9 +10,9 @@
"watch": "Spremljaj.",
"ignore": "Prezri.",
"watching": "Spremljano",
- "not-watching": "Not Watching",
+ "not-watching": "Ni spremljano",
"ignoring": "Prezrto",
- "watching.description": "Show topics in unread and recent",
+ "watching.description": "Prikaži teme v nedavno in nazadnje",
"not-watching.description": "Do not show topics in unread, show in recent",
"ignoring.description": "Do not show topics in unread and recent",
"watching.message": "You are now watching updates from this category and all subcategories",
diff --git a/public/language/sr/admin/extend/rewards.json b/public/language/sr/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/sr/admin/extend/rewards.json
+++ b/public/language/sr/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/sv/admin/extend/rewards.json b/public/language/sv/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/sv/admin/extend/rewards.json
+++ b/public/language/sv/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/th/admin/extend/rewards.json b/public/language/th/admin/extend/rewards.json
index 5383a90b33..df89d441a7 100644
--- a/public/language/th/admin/extend/rewards.json
+++ b/public/language/th/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
- "control-panel": "Rewards Control",
- "new-reward": "New Reward",
"alert.delete-success": "Successfully deleted reward",
"alert.no-inputs-found": "Illegal reward - no inputs found!",
diff --git a/public/language/tr/admin/extend/rewards.json b/public/language/tr/admin/extend/rewards.json
index ff2dca964a..282c929757 100644
--- a/public/language/tr/admin/extend/rewards.json
+++ b/public/language/tr/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Sil",
"enable": "Etkinleştir",
"disable": "Etkinsizleştir",
- "control-panel": "Ödül Kontrol Paneli",
- "new-reward": "Yeni Ödül Ekle",
"alert.delete-success": "Ödül başarıyla silindi",
"alert.no-inputs-found": "Usulsüz ödül - girdi bulunamadı!",
diff --git a/public/language/uk/admin/extend/rewards.json b/public/language/uk/admin/extend/rewards.json
index a3ba8e3b5c..47a0dd459d 100644
--- a/public/language/uk/admin/extend/rewards.json
+++ b/public/language/uk/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Видалити",
"enable": "Увімкнути",
"disable": "Вимкнути",
- "control-panel": "Керування нагородами",
- "new-reward": "Нова нагорода",
"alert.delete-success": "Нагороду успішно видалено",
"alert.no-inputs-found": "Невірна нагорода — поля пусті!",
diff --git a/public/language/vi/admin/extend/rewards.json b/public/language/vi/admin/extend/rewards.json
index 89c006fe45..d7a803ae6a 100644
--- a/public/language/vi/admin/extend/rewards.json
+++ b/public/language/vi/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "Xóa",
"enable": "Bật",
"disable": "Tắt",
- "control-panel": "Kiểm Soát Phần Thưởng",
- "new-reward": "Phần Thưởng Mới",
"alert.delete-success": "Đã xóa thành công phần thưởng",
"alert.no-inputs-found": "Phần thưởng không hợp lệ - không tìm thấy đầu vào!",
diff --git a/public/language/vi/admin/manage/privileges.json b/public/language/vi/admin/manage/privileges.json
index 15ae622bc9..a41a2edd82 100644
--- a/public/language/vi/admin/manage/privileges.json
+++ b/public/language/vi/admin/manage/privileges.json
@@ -59,5 +59,5 @@
"alert.admin-warning": "Quản trị viên ngầm có tất cả các đặc quyền",
"alert.copyPrivilegesFrom-title": "Chọn một danh mục để sao chép từ",
"alert.copyPrivilegesFrom-warning": "Điều này sẽ sao chép %1 từ danh mục đã chọn.",
- "alert.copyPrivilegesFromGroup-warning": "This will copy this group's set of %1 from the selected category."
+ "alert.copyPrivilegesFromGroup-warning": "Thao tác này sẽ sao chép cài đặt %1 của nhóm này từ danh mục đã chọn."
}
\ No newline at end of file
diff --git a/public/language/zh-CN/admin/extend/rewards.json b/public/language/zh-CN/admin/extend/rewards.json
index 12a4336ef0..7646b33976 100644
--- a/public/language/zh-CN/admin/extend/rewards.json
+++ b/public/language/zh-CN/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "删除",
"enable": "启用",
"disable": "禁用",
- "control-panel": "奖励控制",
- "new-reward": "新奖励",
"alert.delete-success": "已成功删除奖励",
"alert.no-inputs-found": "非法奖励 - 输入为空!",
diff --git a/public/language/zh-TW/admin/extend/rewards.json b/public/language/zh-TW/admin/extend/rewards.json
index 19ef749362..924bc85f71 100644
--- a/public/language/zh-TW/admin/extend/rewards.json
+++ b/public/language/zh-TW/admin/extend/rewards.json
@@ -8,8 +8,6 @@
"delete": "刪除",
"enable": "啟用",
"disable": "禁用",
- "control-panel": "獎勵控制",
- "new-reward": "新獎勵",
"alert.delete-success": "已成功刪除獎勵",
"alert.no-inputs-found": "非法獎勵 - 輸入為空!",
diff --git a/public/less/admin/extend/rewards.less b/public/less/admin/extend/rewards.less
index 2dc84b5bc3..3972b16de3 100644
--- a/public/less/admin/extend/rewards.less
+++ b/public/less/admin/extend/rewards.less
@@ -16,6 +16,36 @@
> li {
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
+ &:last-child {
+ border-bottom: 0;
+ }
+ }
+ }
+
+ .rewards { width: 100%; }
+
+ .well {
+ border-radius: 2px;
+ border-width: 2px;
+ color: #333;
+
+ &.if-block {
+ border-color: @brand-primary;
+ }
+ &.this-block {
+ border-color: @brand-warning;
+ }
+ &.then-block {
+ border-color: @brand-success;
+ }
+ &.reward-block {
+ border-color: @brand-success;
+ background-color: lighten(@brand-success, 15%);
+ color: #fff;
+ a, select, input { color: #fff; }
+ select > option { color: #333; }
+ width: 100%;
+ min-height: 110px;
}
}
}
\ No newline at end of file
diff --git a/public/less/admin/manage/groups.less b/public/less/admin/manage/groups.less
index dda50c5c54..2183cc748c 100644
--- a/public/less/admin/manage/groups.less
+++ b/public/less/admin/manage/groups.less
@@ -35,4 +35,18 @@
[component="category/list"] li {
cursor: pointer;
}
+
+ .fa-nbb-none {
+ border: 1px dotted black;
+ height: 35px;
+ width: 35px;
+ }
+
+ .fa-icons .fa-nbb-none {
+ vertical-align: -6px;
+ }
+
+ #group-icon-preview.fa-nbb-none {
+ display: none;
+ }
}
\ No newline at end of file
diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml
index 8ac39fe982..734b64ff24 100644
--- a/public/openapi/write.yaml
+++ b/public/openapi/write.yaml
@@ -52,6 +52,8 @@ paths:
$ref: 'write/users.yaml'
/users/{uid}:
$ref: 'write/users/uid.yaml'
+ /users/{uid}/picture:
+ $ref: 'write/users/uid/picture.yaml'
/users/{uid}/content:
$ref: 'write/users/uid/content.yaml'
/users/{uid}/account:
diff --git a/public/openapi/write/users/uid/picture.yaml b/public/openapi/write/users/uid/picture.yaml
new file mode 100644
index 0000000000..d6498a0af2
--- /dev/null
+++ b/public/openapi/write/users/uid/picture.yaml
@@ -0,0 +1,43 @@
+put:
+ tags:
+ - users
+ summary: update user picture or icon background colour
+ parameters:
+ - in: path
+ name: uid
+ schema:
+ type: integer
+ required: true
+ description: uid of the user
+ example: 1
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The source of the picture
+ enum: ['default', 'uploaded', 'external']
+ example: default
+ url:
+ type: string
+ description: Only used for `external` type, specifies the source of the external image to use as avatar
+ example: ''
+ bgColor:
+ type: string
+ description: A hexadecimal colour representation
+ example: '#ff0000'
+ responses:
+ '200':
+ description: successfully updated user picture
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../components/schemas/Status.yaml#/Status
+ response:
+ type: object
\ No newline at end of file
diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js
index 064b2fa79e..b51e8e4189 100644
--- a/public/src/admin/extend/plugins.js
+++ b/public/src/admin/extend/plugins.js
@@ -79,6 +79,12 @@ define('admin/extend/plugins', [
callback: toggleActivate,
},
},
+ onShown: function () {
+ const saveEl = this.querySelector('button.btn-primary');
+ if (saveEl) {
+ saveEl.focus();
+ }
+ },
});
});
} else {
diff --git a/public/src/admin/extend/rewards.js b/public/src/admin/extend/rewards.js
index 3850b24017..7df62a7e15 100644
--- a/public/src/admin/extend/rewards.js
+++ b/public/src/admin/extend/rewards.js
@@ -95,13 +95,13 @@ define('admin/extend/rewards', [], function () {
html += '
';
diff --git a/public/src/modules/accounts/picture.js b/public/src/modules/accounts/picture.js
index a97e0fb891..3893f2e27a 100644
--- a/public/src/modules/accounts/picture.js
+++ b/public/src/modules/accounts/picture.js
@@ -2,7 +2,8 @@
define('accounts/picture', [
'pictureCropper',
-], (pictureCropper) => {
+ 'api',
+], (pictureCropper, api) => {
const Picture = {};
Picture.openChangeModal = () => {
@@ -89,14 +90,10 @@ define('accounts/picture', [
var type = modal.find('.list-group-item.active').attr('data-type');
const iconBgColor = document.querySelector('.modal.picture-switcher input[type="radio"]:checked').value || 'transparent';
- changeUserPicture(type, iconBgColor, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
-
+ changeUserPicture(type, iconBgColor).then(() => {
Picture.updateHeader(type === 'default' ? '' : modal.find('.list-group-item.active img').attr('src'), iconBgColor);
ajaxify.refresh();
- });
+ }).catch(app.alertError);
}
function onCloseModal() {
@@ -212,12 +209,8 @@ define('accounts/picture', [
});
}
- function changeUserPicture(type, bgColor, callback) {
- socket.emit('user.changePicture', {
- type,
- bgColor,
- uid: ajaxify.data.theirid,
- }, callback);
+ function changeUserPicture(type, bgColor) {
+ return api.put(`/users/${ajaxify.data.theirid}/picture`, { type, bgColor });
}
return Picture;
diff --git a/src/api/categories.js b/src/api/categories.js
index 3486df1ad5..118dd0a258 100644
--- a/src/api/categories.js
+++ b/src/api/categories.js
@@ -65,10 +65,24 @@ categoriesAPI.setPrivilege = async (caller, data) => {
if (!userExists && !groupExists) {
throw new Error('[[error:no-user-or-group]]');
}
-
- await privileges.categories[data.set ? 'give' : 'rescind'](
- Array.isArray(data.privilege) ? data.privilege : [data.privilege], data.cid, data.member
- );
+ const privs = Array.isArray(data.privilege) ? data.privilege : [data.privilege];
+ const type = data.set ? 'give' : 'rescind';
+ if (!privs.length) {
+ throw new Error('[[error:invalid-data]]');
+ }
+ if (parseInt(data.cid, 10) === 0) {
+ const adminPrivs = privs.filter(priv => privileges.admin.privilegeList.includes(priv));
+ const globalPrivs = privs.filter(priv => privileges.global.privilegeList.includes(priv));
+ if (adminPrivs.length) {
+ await privileges.admin[type](adminPrivs, data.member);
+ }
+ if (globalPrivs.length) {
+ await privileges.global[type](globalPrivs, data.member);
+ }
+ } else {
+ const categoryPrivs = privs.filter(priv => privileges.categories.privilegeList.includes(priv));
+ await privileges.categories[type](categoryPrivs, data.cid, data.member);
+ }
await events.log({
uid: caller.uid,
diff --git a/src/api/users.js b/src/api/users.js
index b7deaed080..cbd64e7fd5 100644
--- a/src/api/users.js
+++ b/src/api/users.js
@@ -227,14 +227,9 @@ usersAPI.unban = async function (caller, data) {
async function isPrivilegedOrSelfAndPasswordMatch(caller, data) {
const { uid } = caller;
const isSelf = parseInt(uid, 10) === parseInt(data.uid, 10);
+ const canEdit = await privileges.users.canEdit(uid, data.uid);
- const [isAdmin, isTargetAdmin, isGlobalMod] = await Promise.all([
- user.isAdministrator(uid),
- user.isAdministrator(data.uid),
- user.isGlobalModerator(uid),
- ]);
-
- if ((isTargetAdmin && !isAdmin) || (!isSelf && !(isAdmin || isGlobalMod))) {
+ if (!canEdit) {
throw new Error('[[error:no-privileges]]');
}
const [hasPassword, passwordMatch] = await Promise.all([
@@ -341,3 +336,44 @@ usersAPI.search = async function (caller, data) {
filters: filters,
});
};
+
+usersAPI.changePicture = async (caller, data) => {
+ if (!data) {
+ throw new Error('[[error:invalid-data]]');
+ }
+
+ const { type, url } = data;
+ let picture = '';
+
+ await user.checkMinReputation(caller.uid, data.uid, 'min:rep:profile-picture');
+ const canEdit = await privileges.users.canEdit(caller.uid, data.uid);
+ if (!canEdit) {
+ throw new Error('[[error:no-privileges]]');
+ }
+
+ if (type === 'default') {
+ picture = '';
+ } else if (type === 'uploaded') {
+ picture = await user.getUserField(data.uid, 'uploadedpicture');
+ } else if (type === 'external' && url) {
+ picture = validator.escape(url);
+ } else {
+ const returnData = await plugins.hooks.fire('filter:user.getPicture', {
+ uid: caller.uid,
+ type: type,
+ picture: undefined,
+ });
+ picture = returnData && returnData.picture;
+ }
+
+ const validBackgrounds = await user.getIconBackgrounds(caller.uid);
+ if (!validBackgrounds.includes(data.bgColor)) {
+ data.bgColor = validBackgrounds[0];
+ }
+
+ await user.updateProfile(caller.uid, {
+ uid: data.uid,
+ picture: picture,
+ 'icon:bgColor': data.bgColor,
+ }, ['picture', 'icon:bgColor']);
+};
diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js
index 359355da87..c3a4cb6d35 100644
--- a/src/controllers/accounts/helpers.js
+++ b/src/controllers/accounts/helpers.js
@@ -43,13 +43,13 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
userData.emailClass = 'hide';
- if (!isAdmin && !isGlobalModerator && !isSelf && (!userSettings.showemail || meta.config.hideEmail)) {
+ if (!results.canEdit && (!userSettings.showemail || meta.config.hideEmail)) {
userData.email = '';
} else if (!userSettings.showemail) {
userData.emailClass = '';
}
- if (!isAdmin && !isGlobalModerator && !isSelf && (!userSettings.showfullname || meta.config.hideFullname)) {
+ if (!results.canEdit && (!userSettings.showfullname || meta.config.hideFullname)) {
userData.fullname = '';
}
@@ -78,7 +78,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
userData.isSelf = isSelf;
userData.isFollowing = results.isFollowing;
userData.hasPrivateChat = results.hasPrivateChat;
- userData.showHidden = isSelf || isAdmin || (isGlobalModerator && !results.isTargetAdmin);
+ userData.showHidden = results.canEdit; // remove in v1.19.0
userData.groups = Array.isArray(results.groups) && results.groups.length ? results.groups[0] : [];
userData.disableSignatures = meta.config.disableSignatures === 1;
userData['reputation:disabled'] = meta.config['reputation:disabled'] === 1;
diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js
index 132c0b33f7..4340b16508 100644
--- a/src/controllers/authentication.js
+++ b/src/controllers/authentication.js
@@ -138,16 +138,14 @@ async function addToApprovalQueue(req, userData) {
return { message: message };
}
-authenticationController.registerComplete = function (req, res, next) {
- // For the interstitials that respond, execute the callback with the form body
- plugins.hooks.fire('filter:register.interstitial', {
- req,
- userData: req.session.registration,
- interstitials: [],
- }, async (err, data) => {
- if (err) {
- return next(err);
- }
+authenticationController.registerComplete = async function (req, res) {
+ try {
+ // For the interstitials that respond, execute the callback with the form body
+ const data = await plugins.hooks.fire('filter:register.interstitial', {
+ req,
+ userData: req.session.registration,
+ interstitials: [],
+ });
const callbacks = data.interstitials.reduce((memo, cur) => {
if (cur.hasOwnProperty('callback') && typeof cur.callback === 'function') {
@@ -165,13 +163,10 @@ authenticationController.registerComplete = function (req, res, next) {
return memo;
}, []);
- const done = function (err, data) {
+ const done = function (data) {
delete req.session.registration;
- if (err) {
- return res.redirect(`${nconf.get('relative_path')}/?register=${encodeURIComponent(err.message)}`);
- }
- if (!err && data && data.message) {
+ if (data && data.message) {
return res.redirect(`${nconf.get('relative_path')}/?register=${encodeURIComponent(data.message)}`);
}
@@ -199,8 +194,7 @@ authenticationController.registerComplete = function (req, res, next) {
if (!data) {
return winston.warn('[register] Interstitial callbacks processed with no errors, but one or more interstitials remain. This is likely an issue with one of the interstitials not properly handling a null case or invalid value.');
}
-
- done();
+ done(data);
} else {
// Update user hash, clear registration data in session
const payload = req.session.registration;
@@ -217,7 +211,10 @@ authenticationController.registerComplete = function (req, res, next) {
await user.setUserFields(uid, payload);
done();
}
- });
+ } catch (err) {
+ delete req.session.registration;
+ res.redirect(`${nconf.get('relative_path')}/?register=${encodeURIComponent(err.message)}`);
+ }
};
authenticationController.registerAbort = function (req, res) {
diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js
index 43aa2c0ccc..2469ae4b2a 100644
--- a/src/controllers/write/users.js
+++ b/src/controllers/write/users.js
@@ -76,6 +76,11 @@ Users.deleteMany = async (req, res) => {
helpers.formatApiResponse(200, res);
};
+Users.changePicture = async (req, res) => {
+ await api.users.changePicture(req, { ...req.body, uid: req.params.uid });
+ helpers.formatApiResponse(200, res);
+};
+
Users.updateSettings = async (req, res) => {
const settings = await api.users.updateSettings(req, { ...req.body, uid: req.params.uid });
helpers.formatApiResponse(200, res, settings);
diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js
index 0829bbf8af..67daab7b16 100644
--- a/src/database/mongo/hash.js
+++ b/src/database/mongo/hash.js
@@ -14,6 +14,9 @@ module.exports = function (module) {
}
const writeData = helpers.serializeData(data);
+ if (!Object.keys(writeData).length) {
+ return;
+ }
try {
if (isArray) {
const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
@@ -39,9 +42,18 @@ module.exports = function (module) {
const writeData = data.map(helpers.serializeData);
try {
- const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
- keys.forEach((key, i) => bulk.find({ _key: key }).upsert().updateOne({ $set: writeData[i] }));
- await bulk.execute();
+ let bulk;
+ keys.forEach((key, i) => {
+ if (Object.keys(writeData[i]).length) {
+ if (!bulk) {
+ bulk = module.client.collection('objects').initializeUnorderedBulkOp();
+ }
+ bulk.find({ _key: key }).upsert().updateOne({ $set: writeData[i] });
+ }
+ });
+ if (bulk) {
+ await bulk.execute();
+ }
} catch (err) {
if (err && err.message.startsWith('E11000 duplicate key error')) {
return await module.setObjectBulk(keys, data);
diff --git a/src/database/postgres/hash.js b/src/database/postgres/hash.js
index 1a733a3518..04b38713c2 100644
--- a/src/database/postgres/hash.js
+++ b/src/database/postgres/hash.js
@@ -11,7 +11,9 @@ module.exports = function (module) {
if (data.hasOwnProperty('')) {
delete data[''];
}
-
+ if (!Object.keys(data).length) {
+ return;
+ }
await module.transaction(async (client) => {
const dataString = JSON.stringify(data);
async function setOne(key) {
diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js
index 103cbc4a81..966a36eddd 100644
--- a/src/database/redis/hash.js
+++ b/src/database/redis/hash.js
@@ -41,7 +41,11 @@ module.exports = function (module) {
return;
}
const batch = module.client.batch();
- keys.forEach((k, i) => batch.hmset(k, data[i]));
+ keys.forEach((k, i) => {
+ if (Object.keys(data[i]).length) {
+ batch.hmset(k, data[i]);
+ }
+ });
await helpers.execBatch(batch);
cache.del(keys);
};
diff --git a/src/emailer.js b/src/emailer.js
index 0d6e309b0a..754b794c05 100644
--- a/src/emailer.js
+++ b/src/emailer.js
@@ -324,7 +324,7 @@ Emailer.sendToEmail = async (template, email, language, params) => {
!Plugins.hooks.hasListeners('static:email.send');
try {
if (Plugins.hooks.hasListeners('filter:email.send')) {
- // Deprecated, remove in v1.18.0
+ // Deprecated, remove in v1.19.0
await Plugins.hooks.fire('filter:email.send', data);
} else if (Plugins.hooks.hasListeners('static:email.send')) {
await Plugins.hooks.fire('static:email.send', data);
diff --git a/src/meta/tags.js b/src/meta/tags.js
index daa4d4808c..1f2c68e13f 100644
--- a/src/meta/tags.js
+++ b/src/meta/tags.js
@@ -66,6 +66,7 @@ Tags.parse = async (req, data, meta, link) => {
}, {
rel: 'manifest',
href: `${relative_path}/manifest.webmanifest`,
+ crossorigin: `use-credentials`,
}];
if (plugins.hooks.hasListeners('filter:search.query')) {
diff --git a/src/middleware/user.js b/src/middleware/user.js
index 4adff804d5..b9f195477e 100644
--- a/src/middleware/user.js
+++ b/src/middleware/user.js
@@ -82,7 +82,7 @@ module.exports = function (middleware) {
return !res.headersSent;
}
- // TODO: Remove in v1.18.0
+ // TODO: Remove in v1.19.0
middleware.authenticate = helpers.try(async (req, res, next) => {
winston.warn(`[middleware] middleware.authenticate has been deprecated, page and API routes are now automatically authenticated via setup(Page|API)Route. Use middleware.authenticateRequest (if not using route helper) and middleware.ensureLoggedIn instead. (request path: ${req.path})`);
if (!await authenticate(req, res)) {
@@ -101,7 +101,7 @@ module.exports = function (middleware) {
next();
});
- // TODO: Remove in v1.18.0
+ // TODO: Remove in v1.19.0
middleware.authenticateOrGuest = (req, res, next) => {
winston.warn(`[middleware] middleware.authenticateOrGuest has been renamed, use middleware.authenticateRequest instead. (request path: ${req.path})`);
middleware.authenticateRequest(req, res, next);
diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js
index d46a1fcdb0..24875ca986 100644
--- a/src/plugins/hooks.js
+++ b/src/plugins/hooks.js
@@ -8,7 +8,7 @@ const utils = require('../utils');
const Hooks = module.exports;
Hooks.deprecatedHooks = {
- 'filter:email.send': 'static:email.send', // 👋 @ 1.18.0
+ 'filter:email.send': 'static:email.send', // 👋 @ 1.19.0
'filter:router.page': 'response:router.page', // 👋 @ 2.0.0
};
diff --git a/src/plugins/index.js b/src/plugins/index.js
index a7e8560dd5..04675f1a45 100644
--- a/src/plugins/index.js
+++ b/src/plugins/index.js
@@ -1,7 +1,6 @@
'use strict';
const fs = require('fs');
-const os = require('os');
const path = require('path');
const winston = require('winston');
const semver = require('semver');
@@ -25,23 +24,6 @@ require('./usage')(Plugins);
Plugins.data = require('./data');
Plugins.hooks = require('./hooks');
-// Backwards compatibility for hooks, remove in v1.18.0
-const _deprecate = async function (...args) {
- const oldMethod = args.shift();
- const newMethod = args.shift();
- const method = args.shift();
- const stack = new Error().stack.toString().split(os.EOL);
- const context = stack[stack.findIndex(line => line.startsWith(' at Object.wrapperCallback')) + 1];
- winston.warn(`[plugins/hooks] ${oldMethod} has been deprecated, call ${newMethod} instead.`);
- winston.warn(`[plugins/hooks] ${context}`);
- return method.apply(Plugins.hooks, args);
-};
-Plugins.registerHook = _deprecate.bind(null, 'Plugins.registerHook', 'Plugins.hooks.register', Plugins.hooks.register);
-Plugins.unregisterHook = _deprecate.bind(null, 'Plugins.unregisterHook', 'Plugins.hooks.unregister', Plugins.hooks.unregister);
-Plugins.fireHook = _deprecate.bind(null, 'Plugins.fireHook', 'Plugins.hooks.fire', Plugins.hooks.fire);
-Plugins.hasListeners = _deprecate.bind(null, 'Plugins.hasListeners', 'Plugins.hooks.hasListeners', Plugins.hooks.hasListeners);
-// end
-
Plugins.getPluginPaths = Plugins.data.getPluginPaths;
Plugins.loadPluginInfo = Plugins.data.loadPluginInfo;
diff --git a/src/privileges/admin.js b/src/privileges/admin.js
index ead7f6fc5e..d98e2c2bca 100644
--- a/src/privileges/admin.js
+++ b/src/privileges/admin.js
@@ -35,6 +35,8 @@ privsAdmin.userPrivilegeList = [
privsAdmin.groupPrivilegeList = privsAdmin.userPrivilegeList.map(privilege => `groups:${privilege}`);
+privsAdmin.privilegeList = privsAdmin.userPrivilegeList.concat(privsAdmin.groupPrivilegeList);
+
// Mapping for a page route (via direct match or regexp) to a privilege
privsAdmin.routeMap = {
dashboard: 'admin:dashboard',
diff --git a/src/privileges/global.js b/src/privileges/global.js
index 563ae3d053..206e7769c3 100644
--- a/src/privileges/global.js
+++ b/src/privileges/global.js
@@ -49,6 +49,8 @@ privsGlobal.userPrivilegeList = [
privsGlobal.groupPrivilegeList = privsGlobal.userPrivilegeList.map(privilege => `groups:${privilege}`);
+privsGlobal.privilegeList = privsGlobal.userPrivilegeList.concat(privsGlobal.groupPrivilegeList);
+
privsGlobal.list = async function () {
async function getLabels() {
return await utils.promiseParallel({
diff --git a/src/privileges/index.js b/src/privileges/index.js
index 4a9c4d2096..6445b07b97 100644
--- a/src/privileges/index.js
+++ b/src/privileges/index.js
@@ -9,20 +9,3 @@ privileges.posts = require('./posts');
privileges.users = require('./users');
require('../promisify')(privileges);
-
-// TODO: backwards compatibility remove in 1.18.0
-[
- 'privilegeLabels',
- 'userPrivilegeList',
- 'groupPrivilegeList',
- 'privilegeList',
-].forEach((fieldName) => {
- Object.defineProperty(privileges, fieldName, {
- configurable: true,
- enumerable: true,
- get: function () {
- console.warn(`[deprecated] privileges.${fieldName} is deprecated. Use privileges.categories.${fieldName}`);
- return privileges.categories[fieldName];
- },
- });
-});
diff --git a/src/routes/write/users.js b/src/routes/write/users.js
index 02a64e134f..418fa9897d 100644
--- a/src/routes/write/users.js
+++ b/src/routes/write/users.js
@@ -22,6 +22,7 @@ function authenticatedRoutes() {
setupApiRoute(router, 'get', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.get);
setupApiRoute(router, 'put', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.update);
setupApiRoute(router, 'delete', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.delete);
+ setupApiRoute(router, 'put', '/:uid/picture', [...middlewares, middleware.assert.user], controllers.write.users.changePicture);
setupApiRoute(router, 'delete', '/:uid/content', [...middlewares, middleware.assert.user], controllers.write.users.deleteContent);
setupApiRoute(router, 'delete', '/:uid/account', [...middlewares, middleware.assert.user], controllers.write.users.deleteAccount);
diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js
index a5a2fbbea7..7de2ef7165 100644
--- a/src/socket.io/user/picture.js
+++ b/src/socket.io/user/picture.js
@@ -3,42 +3,17 @@
const user = require('../../user');
const plugins = require('../../plugins');
+const websockets = require('../index');
+const api = require('../../api');
+
module.exports = function (SocketUser) {
SocketUser.changePicture = async function (socket, data) {
if (!socket.uid) {
throw new Error('[[error:invalid-uid]]');
}
- if (!data) {
- throw new Error('[[error:invalid-data]]');
- }
-
- const { type } = data;
- let picture = '';
- await user.isAdminOrGlobalModOrSelf(socket.uid, data.uid);
- if (type === 'default') {
- picture = '';
- } else if (type === 'uploaded') {
- picture = await user.getUserField(data.uid, 'uploadedpicture');
- } else {
- const returnData = await plugins.hooks.fire('filter:user.getPicture', {
- uid: socket.uid,
- type: type,
- picture: undefined,
- });
- picture = returnData && returnData.picture;
- }
-
- const validBackgrounds = await user.getIconBackgrounds(socket.uid);
- if (!validBackgrounds.includes(data.bgColor)) {
- data.bgColor = validBackgrounds[0];
- }
-
- await user.updateProfile(socket.uid, {
- uid: data.uid,
- picture: picture,
- 'icon:bgColor': data.bgColor,
- }, ['picture', 'icon:bgColor']);
+ websockets.warnDeprecated(socket, 'PUT /api/v3/users/:uid/picture');
+ await api.users.changePicture(socket, data);
};
SocketUser.removeUploadedPicture = async function (socket, data) {
diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js
index 4f757943ac..2de4160ba5 100644
--- a/src/socket.io/user/profile.js
+++ b/src/socket.io/user/profile.js
@@ -6,6 +6,7 @@ const api = require('../../api');
const user = require('../../user');
const events = require('../../events');
const notifications = require('../../notifications');
+const privileges = require('../../privileges');
const db = require('../../database');
const plugins = require('../../plugins');
const sockets = require('..');
@@ -31,10 +32,10 @@ module.exports = function (SocketUser) {
};
SocketUser.uploadCroppedPicture = async function (socket, data) {
- if (!socket.uid) {
+ if (!socket.uid || !(await privileges.users.canEdit(socket.uid, data.uid))) {
throw new Error('[[error:no-privileges]]');
}
- await user.isAdminOrGlobalModOrSelf(socket.uid, data.uid);
+
await user.checkMinReputation(socket.uid, data.uid, 'min:rep:profile-picture');
data.callerUid = socket.uid;
return await user.uploadCroppedPicture(data);
diff --git a/src/views/admin/extend/rewards.tpl b/src/views/admin/extend/rewards.tpl
index bd226cc39d..456ab7d379 100644
--- a/src/views/admin/extend/rewards.tpl
+++ b/src/views/admin/extend/rewards.tpl
@@ -1,76 +1,82 @@
-