mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-13 18:02:21 +02:00
Merge commit '507517fce5248ad37f6a239fce16ae92d0e0f5b0' into v1.18.x
This commit is contained in:
44
CHANGELOG.md
44
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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "Изтриване",
|
||||
"enable": "Включване",
|
||||
"disable": "Изключване",
|
||||
"control-panel": "Управление на наградите",
|
||||
"new-reward": "Нова награда",
|
||||
|
||||
"alert.delete-success": "Наградата е изтрита успешно",
|
||||
"alert.no-inputs-found": "Неправомерна награда — няма нищо въведено!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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 !",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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.",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "מחק",
|
||||
"enable": "הפעל",
|
||||
"disable": "השבת",
|
||||
"control-panel": "בקרת תגמולים",
|
||||
"new-reward": "תגמול חדש",
|
||||
|
||||
"alert.delete-success": "תגמול נמחק בהצלחה",
|
||||
"alert.no-inputs-found": "תגמול לא חוקי - לא נמצא מידע!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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 <strong>%1</strong> to <strong>all categories</strong>?",
|
||||
"alert.confirm-copyToAllGroup": "Are you sure you wish to apply this group's set of <strong>%1</strong> to <strong>all categories</strong>?",
|
||||
"alert.confirm-copyToChildren": "Are you sure you wish to apply this set of <strong>%1</strong> to <strong>all descendant (child) categories</strong>?",
|
||||
"alert.confirm-copyToChildrenGroup": "Are you sure you wish to apply this group's set of <strong>%1</strong> to <strong>all descendant (child) categories</strong>?",
|
||||
"alert.confirm-copyToAll": "Sei sicuro di voler applicare questa serie di <strong>%1</strong> a <strong>tutte le categorie</strong>?",
|
||||
"alert.confirm-copyToAllGroup": "Sei sicuro di voler applicare questa serie di <strong>%1</strong> del gruppo a <strong>tutte le categorie</strong>?",
|
||||
"alert.confirm-copyToChildren": "Sei sicuro di voler applicare questa serie di <strong>%1</strong> a <strong>tutte le categorie discendenti (figli)</strong>?",
|
||||
"alert.confirm-copyToChildrenGroup": "Sei sicuro di voler applicare questa serie di <strong>%1</strong> del questo gruppo a <strong>tutte le categorie discendenti (figli)</strong>?",
|
||||
"alert.no-undo": "<em>Questa azione non può essere annullata.</em>",
|
||||
"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 <strong>%1</strong> from the selected category.",
|
||||
"alert.copyPrivilegesFromGroup-warning": "This will copy this group's set of <strong>%1</strong> from the selected category."
|
||||
"alert.copyPrivilegesFrom-title": "Seleziona una categoria da cui copiare",
|
||||
"alert.copyPrivilegesFrom-warning": "Questo copierà <strong>1%</strong> dalla categoria selezionata.",
|
||||
"alert.copyPrivilegesFromGroup-warning": "Questo copierà la serie di <strong>%1</strong> da questo gruppo dalla categoria selezionata."
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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.",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "削除",
|
||||
"enable": "有効",
|
||||
"disable": "無効",
|
||||
"control-panel": "報酬コントロール",
|
||||
"new-reward": "新しい報酬",
|
||||
|
||||
"alert.delete-success": "報酬を削除しました",
|
||||
"alert.no-inputs-found": "違法報酬 - 入力が見つかりません!",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "삭제",
|
||||
"enable": "활성화",
|
||||
"disable": "비활성화",
|
||||
"control-panel": "보상 제어판",
|
||||
"new-reward": "새로운 보상",
|
||||
|
||||
"alert.delete-success": "성공적으로 보상을 삭제했습니다.",
|
||||
"alert.no-inputs-found": "잘못된 보상 - 입력값이 없습니다!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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 ",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "Удалить",
|
||||
"enable": "Включить",
|
||||
"disable": "Выключить",
|
||||
"control-panel": "Управление наградами",
|
||||
"new-reward": "Новая награда",
|
||||
|
||||
"alert.delete-success": "Награда успешно удалена",
|
||||
"alert.no-inputs-found": "Некорректная награда!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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!",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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ı!",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "Видалити",
|
||||
"enable": "Увімкнути",
|
||||
"disable": "Вимкнути",
|
||||
"control-panel": "Керування нагородами",
|
||||
"new-reward": "Нова нагорода",
|
||||
|
||||
"alert.delete-success": "Нагороду успішно видалено",
|
||||
"alert.no-inputs-found": "Невірна нагорода — поля пусті!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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 <strong>%1</strong> từ danh mục đã chọn.",
|
||||
"alert.copyPrivilegesFromGroup-warning": "This will copy this group's set of <strong>%1</strong> from the selected category."
|
||||
"alert.copyPrivilegesFromGroup-warning": "Thao tác này sẽ sao chép cài đặt <strong>%1</strong> của nhóm này từ danh mục đã chọn."
|
||||
}
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "删除",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"control-panel": "奖励控制",
|
||||
"new-reward": "新奖励",
|
||||
|
||||
"alert.delete-success": "已成功删除奖励",
|
||||
"alert.no-inputs-found": "非法奖励 - 输入为空!",
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"delete": "刪除",
|
||||
"enable": "啟用",
|
||||
"disable": "禁用",
|
||||
"control-panel": "獎勵控制",
|
||||
"new-reward": "新獎勵",
|
||||
|
||||
"alert.delete-success": "已成功刪除獎勵",
|
||||
"alert.no-inputs-found": "非法獎勵 - 輸入為空!",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
43
public/openapi/write/users/uid/picture.yaml
Normal file
43
public/openapi/write/users/uid/picture.yaml
Normal file
@@ -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
|
||||
@@ -79,6 +79,12 @@ define('admin/extend/plugins', [
|
||||
callback: toggleActivate,
|
||||
},
|
||||
},
|
||||
onShown: function () {
|
||||
const saveEl = this.querySelector('button.btn-primary');
|
||||
if (saveEl) {
|
||||
saveEl.focus();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -95,13 +95,13 @@ define('admin/extend/rewards', [], function () {
|
||||
html += '<label for="' + input.name + '">' + input.label + '<br />';
|
||||
switch (input.type) {
|
||||
case 'select':
|
||||
html += '<select name="' + input.name + '">';
|
||||
html += '<select class="form-control" name="' + input.name + '">';
|
||||
input.values.forEach(function (value) {
|
||||
html += '<option value="' + value.value + '">' + value.name + '</option>';
|
||||
});
|
||||
break;
|
||||
case 'text':
|
||||
html += '<input type="text" name="' + input.name + '" />';
|
||||
html += '<input type="text" class="form-control" name="' + input.name + '" />';
|
||||
break;
|
||||
}
|
||||
html += '</label><br />';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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']);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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];
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,76 +1,82 @@
|
||||
<div id="rewards" class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">[[admin/extend/rewards:rewards]]</div>
|
||||
<div class="panel-body">
|
||||
<ul id="active">
|
||||
{{{ each active }}}
|
||||
<li data-rid="{active.rid}" data-id="{active.id}">
|
||||
<form class="main inline-block">
|
||||
<div class="well inline-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-if-users]]</label><br />
|
||||
<select name="condition" data-selected="{active.condition}">
|
||||
{{{ each conditions }}}
|
||||
<option value="{conditions.condition}">{conditions.name}</option>
|
||||
{{{ end }}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="well inline-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-is]]</label><br />
|
||||
<select name="conditional" data-selected="{active.conditional}">
|
||||
{{{ each conditionals }}}
|
||||
<option value="{conditionals.conditional}">{conditionals.name}</option>
|
||||
{{{ end }}}
|
||||
</select>
|
||||
<input type="text" name="value" value="{active.value}" />
|
||||
</div>
|
||||
<div class="well inline-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-then]]</label><br />
|
||||
<select name="rid" data-selected="{active.rid}">
|
||||
<!-- BEGIN ../../rewards -->
|
||||
<option value="{rewards.rid}">{rewards.name}</option>
|
||||
<!-- END ../../rewards -->
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="rewards inline-block">
|
||||
<div class="inputs well inline-block"></div>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="pull-right">
|
||||
<div class="panel-body inline-block">
|
||||
<form class="main">
|
||||
<label for="claimable">[[admin/extend/rewards:max-claims]]</label><br />
|
||||
<input type="text" name="claimable" value="{active.claimable}" placeholder="1" />
|
||||
<small>[[admin/extend/rewards:zero-infinite]]</small>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-body inline-block">
|
||||
<button class="btn btn-danger delete">[[admin/extend/rewards:delete]]</button>
|
||||
<!-- IF active.disabled -->
|
||||
<button class="btn btn-success toggle">[[admin/extend/rewards:enable]]</button>
|
||||
<!-- ELSE -->
|
||||
<button class="btn btn-warning toggle">[[admin/extend/rewards:disable]]</button>
|
||||
<!-- ENDIF active.disabled -->
|
||||
<div id="rewards">
|
||||
<ul id="active">
|
||||
{{{ each active }}}
|
||||
<li data-rid="{active.rid}" data-id="{active.id}">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-lg-8">
|
||||
<form class="main inline-block">
|
||||
<div class="well inline-block if-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-if-users]]</label><br />
|
||||
<select class="form-control" name="condition" data-selected="{active.condition}">
|
||||
{{{ each conditions }}}
|
||||
<option value="{conditions.condition}">{conditions.name}</option>
|
||||
{{{ end }}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="well inline-block this-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-is]]</label><br />
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<select class="form-control" name="conditional" data-selected="{active.conditional}">
|
||||
{{{ each conditionals }}}
|
||||
<option value="{conditionals.conditional}">{conditionals.name}</option>
|
||||
{{{ end }}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<input class="form-control" type="text" name="value" value="{active.value}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
</ul>
|
||||
<div class="well inline-block then-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-then]]</label><br />
|
||||
<select class="form-control" name="rid" data-selected="{active.rid}">
|
||||
<!-- BEGIN ../../rewards -->
|
||||
<option value="{rewards.rid}">{rewards.name}</option>
|
||||
<!-- END ../../rewards -->
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-lg-4">
|
||||
<form class="rewards inline-block">
|
||||
<div class="inputs well inline-block reward-block"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 acp-sidebar">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">[[admin/extend/rewards:control-panel]]</div>
|
||||
<div class="panel-body">
|
||||
<button class="btn btn-success btn-md" id="new">[[admin/extend/rewards:new-reward]]</button>
|
||||
<button class="btn btn-primary btn-md" id="save">[[global:save_changes]]</button>
|
||||
<div class="pull-left">
|
||||
<div class="panel-body inline-block">
|
||||
<form class="main">
|
||||
<label for="claimable">[[admin/extend/rewards:max-claims]] <small>[[admin/extend/rewards:zero-infinite]]</small></label><br />
|
||||
<input class="form-control" type="text" name="claimable" value="{active.claimable}" placeholder="1" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
<div class="panel-body inline-block">
|
||||
<button class="btn btn-danger delete">[[admin/extend/rewards:delete]]</button>
|
||||
<!-- IF active.disabled -->
|
||||
<button class="btn btn-success toggle">[[admin/extend/rewards:enable]]</button>
|
||||
<!-- ELSE -->
|
||||
<button class="btn btn-warning toggle">[[admin/extend/rewards:disable]]</button>
|
||||
<!-- ENDIF active.disabled -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="floating-button">
|
||||
<button id="new" class="mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored" >
|
||||
<i class="material-icons">add</i>
|
||||
</button>
|
||||
|
||||
<button id="save" class="mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored primary">
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -2,8 +2,10 @@
|
||||
<div class="panel-heading">
|
||||
[[admin/dashboard:forum-traffic]]
|
||||
<div class="pull-right">
|
||||
<a id="view-as-json" href="{config.relative_path}/api/v3/admin/analytics/{set}?type=hourly"><i class="fa fa-terminal"></i></a>
|
||||
<i class="fa fa-expand"></i>
|
||||
<a href="#"><i class="fa fa-fw fa-expand"></i></a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a target="_blank" id="view-as-json" href="{config.relative_path}/api/v3/admin/analytics/{set}?type=hourly"><i class="fa fa-fw fa-terminal"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('Hash methods', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should set multiple keys to different okjects', async () => {
|
||||
it('should set multiple keys to different objects', async () => {
|
||||
const keys = ['bulkKey1', 'bulkKey2'];
|
||||
const data = [{ foo: '1' }, { baz: 'baz' }];
|
||||
|
||||
@@ -80,6 +80,39 @@ describe('Hash methods', () => {
|
||||
const result = await db.getObjects(keys);
|
||||
assert.deepStrictEqual(result, data);
|
||||
});
|
||||
|
||||
it('should not error if object is empty', async () => {
|
||||
const keys = ['bulkKey3', 'bulkKey4'];
|
||||
const data = [{ foo: '1' }, { }];
|
||||
|
||||
await db.setObjectBulk(keys, data);
|
||||
const result = await db.getObjects(keys);
|
||||
assert.deepStrictEqual(result, [{ foo: '1' }, null]);
|
||||
});
|
||||
|
||||
it('should not error if object is empty', async () => {
|
||||
const keys = ['bulkKey5'];
|
||||
const data = [{ }];
|
||||
|
||||
await db.setObjectBulk(keys, data);
|
||||
const result = await db.getObjects(keys);
|
||||
assert.deepStrictEqual(result, [null]);
|
||||
});
|
||||
|
||||
it('should not error if object is empty', async () => {
|
||||
const keys = ['bulkKey6', 'bulkKey7'];
|
||||
const data = {};
|
||||
|
||||
await db.setObject(keys, data);
|
||||
const result = await db.getObjects(keys);
|
||||
assert.deepStrictEqual(result, [null, null]);
|
||||
});
|
||||
|
||||
it('should not error if object is empty', async () => {
|
||||
await db.setObject('emptykey', {});
|
||||
const result = await db.getObject('emptykey');
|
||||
assert.deepStrictEqual(result, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setObjectField()', () => {
|
||||
|
||||
@@ -10,6 +10,16 @@ const utils = require('../../public/src/utils');
|
||||
|
||||
const helpers = module.exports;
|
||||
|
||||
helpers.getCsrfToken = async (jar) => {
|
||||
const { csrf_token: token } = await requestAsync({
|
||||
url: `${nconf.get('url')}/api/config`,
|
||||
json: true,
|
||||
jar,
|
||||
});
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
helpers.loginUser = function (username, password, callback) {
|
||||
const jar = request.jar();
|
||||
|
||||
|
||||
23
test/user.js
23
test/user.js
@@ -5,6 +5,7 @@ const async = require('async');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const nconf = require('nconf');
|
||||
const validator = require('validator');
|
||||
const request = require('request');
|
||||
const requestAsync = require('request-promise-native');
|
||||
const jwt = require('jsonwebtoken');
|
||||
@@ -1070,6 +1071,28 @@ describe('User', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should let you set an external image', async () => {
|
||||
const token = await helpers.getCsrfToken(jar);
|
||||
const body = await requestAsync(`${nconf.get('url')}/api/v3/users/${uid}/picture`, {
|
||||
jar,
|
||||
method: 'put',
|
||||
json: true,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
body: {
|
||||
type: 'external',
|
||||
url: 'https://example.org/picture.jpg',
|
||||
},
|
||||
});
|
||||
|
||||
assert(body && body.status && body.response);
|
||||
assert.strictEqual(body.status.code, 'ok');
|
||||
|
||||
const picture = await User.getUserField(uid, 'picture');
|
||||
assert.strictEqual(picture, validator.escape('https://example.org/picture.jpg'));
|
||||
});
|
||||
|
||||
it('should fail to change user picture with invalid data', (done) => {
|
||||
socketUser.changePicture({ uid: uid }, null, (err) => {
|
||||
assert.equal(err.message, '[[error:invalid-data]]');
|
||||
|
||||
Reference in New Issue
Block a user