2019-11-02 00:06:25 +01:00
const render _settings _custom _user _profile _field = require ( "../templates/settings/custom_user_profile_field.hbs" ) ;
const render _settings _dev _env _email _access = require ( '../templates/settings/dev_env_email_access.hbs' ) ;
const render _settings _api _key _modal = require ( '../templates/settings/api_key_modal.hbs' ) ;
2019-07-09 21:24:00 +02:00
2017-04-06 02:28:57 +02:00
exports . update _email = function ( new _email ) {
2019-11-02 00:06:25 +01:00
const email _input = $ ( '#email_value' ) ;
2017-04-06 02:28:57 +02:00
if ( email _input ) {
email _input . text ( new _email ) ;
}
} ;
2018-02-02 01:24:26 +01:00
exports . update _full _name = function ( new _full _name ) {
2020-02-22 20:39:48 +01:00
const full _name _field = $ ( "#full_name_value" ) ;
2018-02-02 01:24:26 +01:00
if ( full _name _field ) {
full _name _field . text ( new _full _name ) ;
}
// Arguably, this should work more like how the `update_email`
// flow works, where we update the name in the modal on open,
// rather than updating it here, but this works.
2019-11-02 00:06:25 +01:00
const full _name _input = $ ( ".full_name_change_container input[name='full_name']" ) ;
2018-02-02 01:24:26 +01:00
if ( full _name _input ) {
full _name _input . val ( new _full _name ) ;
}
} ;
2019-07-15 23:12:31 +02:00
exports . user _can _change _name = function ( ) {
if ( page _params . is _admin ) {
return true ;
}
if ( page _params . realm _name _changes _disabled || page _params . server _name _changes _disabled ) {
return false ;
}
return true ;
} ;
2019-08-11 16:34:42 +02:00
exports . user _can _change _avatar = function ( ) {
if ( page _params . is _admin ) {
return true ;
}
if ( page _params . realm _avatar _changes _disabled || page _params . server _avatar _changes _disabled ) {
return false ;
}
return true ;
} ;
2018-03-02 21:44:14 +01:00
exports . update _name _change _display = function ( ) {
2019-07-15 23:12:31 +02:00
if ( ! exports . user _can _change _name ( ) ) {
2018-03-02 21:48:23 +01:00
$ ( '#full_name' ) . attr ( 'disabled' , 'disabled' ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_name_tooltip" ) . show ( ) ;
} else {
2018-03-02 21:48:23 +01:00
$ ( '#full_name' ) . attr ( 'disabled' , false ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_name_tooltip" ) . hide ( ) ;
}
} ;
exports . update _email _change _display = function ( ) {
if ( page _params . realm _email _changes _disabled && ! page _params . is _admin ) {
2018-03-02 21:48:23 +01:00
$ ( '#change_email .button' ) . attr ( 'disabled' , 'disabled' ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_email_tooltip" ) . show ( ) ;
} else {
2018-03-02 21:48:23 +01:00
$ ( '#change_email .button' ) . attr ( 'disabled' , false ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_email_tooltip" ) . hide ( ) ;
}
} ;
2019-04-23 04:51:04 +02:00
exports . update _avatar _change _display = function ( ) {
2019-08-11 16:34:42 +02:00
if ( ! exports . user _can _change _avatar ( ) ) {
2019-04-23 04:51:04 +02:00
$ ( '#user_avatar_upload_button .button' ) . attr ( 'disabled' , 'disabled' ) ;
$ ( '#user_avatar_delete_button .button' ) . attr ( 'disabled' , 'disabled' ) ;
} else {
$ ( '#user_avatar_upload_button .button' ) . attr ( 'disabled' , false ) ;
$ ( '#user_avatar_delete_button .button' ) . attr ( 'disabled' , false ) ;
}
} ;
2020-04-02 09:55:16 +02:00
function display _avatar _upload _complete ( ) {
$ ( '#user-avatar-background' ) . css ( { display : 'none' } ) ;
$ ( '#user-avatar-spinner' ) . css ( { display : 'none' } ) ;
$ ( '#user_avatar_upload_button' ) . show ( ) ;
$ ( '#user_avatar_delete_button' ) . show ( ) ;
}
function display _avatar _upload _started ( ) {
$ ( "#user-avatar-source" ) . hide ( ) ;
$ ( '#user-avatar-background' ) . css ( { display : 'block' } ) ;
$ ( '#user-avatar-spinner' ) . css ( { display : 'block' } ) ;
$ ( '#user_avatar_upload_button' ) . hide ( ) ;
$ ( '#user_avatar_delete_button' ) . hide ( ) ;
}
2017-04-06 02:28:57 +02:00
function settings _change _error ( message , xhr ) {
ui _report . error ( message , xhr , $ ( '#account-settings-status' ) . expectOne ( ) ) ;
}
2019-07-10 22:18:45 +02:00
function update _custom _profile _field ( field , method ) {
2019-11-02 00:06:25 +01:00
let field _id ;
2019-07-10 22:18:45 +02:00
if ( method === channel . del ) {
field _id = field ;
} else {
field _id = field . id ;
}
2019-11-02 00:06:25 +01:00
const spinner = $ ( '.custom_user_field[data-field-id="' + field _id +
2019-07-10 22:18:45 +02:00
'"] .custom-field-status' ) . expectOne ( ) ;
loading . make _indicator ( spinner , { text : 'Saving ...' } ) ;
settings _ui . do _settings _change ( method , "/json/users/me/profile_data" ,
{ data : JSON . stringify ( [ field ] ) } , spinner ) ;
}
2018-06-05 12:57:02 +02:00
function update _user _custom _profile _fields ( fields , method ) {
if ( method === undefined ) {
blueslip . error ( "Undefined method in update_user_custom_profile_fields" ) ;
}
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
for ( const field of fields ) {
2019-07-10 22:18:45 +02:00
update _custom _profile _field ( field , method ) ;
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
}
2018-05-25 20:58:14 +02:00
}
2018-10-24 16:32:30 +02:00
exports . append _custom _profile _fields = function ( element _id , user _id ) {
2020-02-05 14:30:59 +01:00
const person = people . get _by _user _id ( user _id ) ;
2019-04-07 17:29:53 +02:00
if ( person . is _bot ) {
return ;
}
2019-11-02 00:06:25 +01:00
const all _custom _fields = page _params . custom _profile _fields ;
const all _field _types = page _params . custom _profile _field _types ;
2019-06-06 11:51:53 +02:00
2020-02-12 07:19:14 +01:00
const all _field _template _types = new Map ( [
[ all _field _types . LONG _TEXT . id , "text" ] ,
[ all _field _types . SHORT _TEXT . id , "text" ] ,
[ all _field _types . CHOICE . id , "choice" ] ,
[ all _field _types . USER . id , "user" ] ,
[ all _field _types . DATE . id , "date" ] ,
[ all _field _types . EXTERNAL _ACCOUNT . id , "text" ] ,
[ all _field _types . URL . id , "url" ] ,
] ) ;
2018-10-24 16:32:30 +02:00
all _custom _fields . forEach ( function ( field ) {
2019-11-02 00:06:25 +01:00
let field _value = people . get _custom _profile _data ( user _id , field . id ) ;
const is _choice _field = field . type === all _field _types . CHOICE . id ;
const field _choices = [ ] ;
2019-06-06 11:51:53 +02:00
2018-12-31 06:41:06 +01:00
if ( field _value === undefined || field _value === null ) {
field _value = { value : "" , rendered _value : "" } ;
}
2019-06-06 11:51:53 +02:00
if ( is _choice _field ) {
2019-11-02 00:06:25 +01:00
const field _choice _dict = JSON . parse ( field . field _data ) ;
for ( const choice in field _choice _dict ) {
2018-10-24 16:32:30 +02:00
if ( choice ) {
field _choices [ field _choice _dict [ choice ] . order ] = {
value : choice ,
text : field _choice _dict [ choice ] . text ,
2018-12-31 06:41:06 +01:00
selected : choice === field _value . value ,
2018-10-24 16:32:30 +02:00
} ;
}
}
}
2019-11-02 00:06:25 +01:00
const html = render _settings _custom _user _profile _field ( {
2018-10-24 16:32:30 +02:00
field : field ,
2020-02-12 07:19:14 +01:00
field _type : all _field _template _types . get ( field . type ) ,
2018-12-31 06:41:06 +01:00
field _value : field _value ,
2019-06-06 11:51:53 +02:00
is _long _text _field : field . type === all _field _types . LONG _TEXT . id ,
is _user _field : field . type === all _field _types . USER . id ,
is _date _field : field . type === all _field _types . DATE . id ,
2018-10-24 16:32:30 +02:00
is _choice _field : is _choice _field ,
field _choices : field _choices ,
} ) ;
$ ( element _id ) . append ( html ) ;
} ) ;
} ;
2018-10-22 10:20:33 +02:00
exports . initialize _custom _date _type _fields = function ( element _id ) {
$ ( element _id ) . find ( ".custom_user_field .datepicker" ) . flatpickr ( {
altInput : true ,
altFormat : "F j, Y" } ) ;
2019-07-29 20:54:30 +02:00
$ ( element _id ) . find ( ".custom_user_field .datepicker" ) . on ( "mouseenter" , function ( ) {
if ( $ ( this ) . val ( ) . length <= 0 ) {
$ ( this ) . parent ( ) . find ( ".remove_date" ) . hide ( ) ;
} else {
$ ( this ) . parent ( ) . find ( ".remove_date" ) . show ( ) ;
}
} ) ;
2019-07-29 20:59:33 +02:00
$ ( element _id ) . find ( ".custom_user_field .remove_date" ) . on ( "click" , function ( ) {
$ ( this ) . parent ( ) . find ( ".custom_user_field_value" ) . val ( "" ) ;
} ) ;
2018-10-22 10:20:33 +02:00
} ;
2019-04-12 15:19:23 +02:00
exports . initialize _custom _user _type _fields = function ( element _id , user _id , is _editable ,
set _handler _on _update ) {
2019-11-02 00:06:25 +01:00
const field _types = page _params . custom _profile _field _types ;
2020-02-03 09:42:50 +01:00
const user _pills = new Map ( ) ;
2018-10-23 19:46:12 +02:00
2020-02-05 14:30:59 +01:00
const person = people . get _by _user _id ( user _id ) ;
2019-04-07 17:29:53 +02:00
if ( person . is _bot ) {
2020-01-16 20:37:29 +01:00
return user _pills ;
2019-04-07 17:29:53 +02:00
}
2018-10-23 19:46:12 +02:00
page _params . custom _profile _fields . forEach ( function ( field ) {
2019-11-02 00:06:25 +01:00
let field _value _raw = people . get _custom _profile _data ( user _id , field . id ) ;
2018-10-23 19:46:12 +02:00
2018-12-31 06:41:06 +01:00
if ( field _value _raw ) {
field _value _raw = field _value _raw . value ;
}
2018-10-23 19:46:12 +02:00
// If field is not editable and field value is null, we don't expect
// pill container for that field and proceed further
if ( field . type === field _types . USER . id && ( field _value _raw || is _editable ) ) {
2019-11-02 00:06:25 +01:00
const pill _container = $ ( element _id ) . find ( '.custom_user_field[data-field-id="' +
2018-10-23 19:46:12 +02:00
field . id + '"] .pill-container' ) . expectOne ( ) ;
2019-11-02 00:06:25 +01:00
const pills = user _pill . create _pills ( pill _container ) ;
2018-10-23 19:46:12 +02:00
function update _custom _user _field ( ) {
2019-11-02 00:06:25 +01:00
const fields = [ ] ;
const user _ids = user _pill . get _user _ids ( pills ) ;
2018-10-23 19:46:12 +02:00
if ( user _ids . length < 1 ) {
fields . push ( field . id ) ;
update _user _custom _profile _fields ( fields , channel . del ) ;
} else {
fields . push ( { id : field . id , value : user _ids } ) ;
update _user _custom _profile _fields ( fields , channel . patch ) ;
}
}
if ( field _value _raw ) {
2019-11-02 00:06:25 +01:00
const field _value = JSON . parse ( field _value _raw ) ;
2018-10-23 19:46:12 +02:00
if ( field _value ) {
field _value . forEach ( function ( pill _user _id ) {
2020-02-05 14:30:59 +01:00
const user = people . get _by _user _id ( pill _user _id ) ;
2018-10-23 19:46:12 +02:00
user _pill . append _user ( user , pills ) ;
} ) ;
}
}
2018-12-31 07:45:33 +01:00
2018-10-23 19:46:12 +02:00
if ( is _editable ) {
2019-11-02 00:06:25 +01:00
const input = pill _container . children ( '.input' ) ;
2018-10-23 19:46:12 +02:00
if ( set _handler _on _update ) {
user _pill . set _up _typeahead _on _pills ( input , pills , update _custom _user _field ) ;
pills . onPillRemove ( function ( ) {
update _custom _user _field ( ) ;
} ) ;
} else {
user _pill . set _up _typeahead _on _pills ( input , pills , function ( ) { } ) ;
}
}
2020-01-16 20:37:29 +01:00
user _pills . set ( field . id , pills ) ;
2018-10-23 19:46:12 +02:00
}
} ) ;
2019-04-07 17:29:53 +02:00
2018-10-23 19:46:12 +02:00
return user _pills ;
} ;
2018-04-04 16:21:05 +02:00
exports . add _custom _profile _fields _to _settings = function ( ) {
2018-06-10 07:03:22 +02:00
if ( ! overlays . settings _open ( ) ) {
return ;
}
2019-11-02 00:06:25 +01:00
const element _id = "#account-settings .custom-profile-fields-form" ;
2018-10-22 10:20:33 +02:00
$ ( element _id ) . html ( "" ) ;
2018-06-07 11:39:12 +02:00
if ( page _params . custom _profile _fields . length > 0 ) {
$ ( "#account-settings #custom-field-header" ) . show ( ) ;
} else {
$ ( "#account-settings #custom-field-header" ) . hide ( ) ;
}
2018-10-24 16:32:30 +02:00
exports . append _custom _profile _fields ( element _id , people . my _current _user _id ( ) ) ;
2019-04-12 15:19:23 +02:00
exports . initialize _custom _user _type _fields ( element _id , people . my _current _user _id ( ) , true , true ) ;
2018-10-22 10:20:33 +02:00
exports . initialize _custom _date _type _fields ( element _id ) ;
2018-04-04 16:21:05 +02:00
} ;
2017-04-06 02:28:57 +02:00
exports . set _up = function ( ) {
2018-02-26 20:09:07 +01:00
// Add custom profile fields elements to user account settings.
2018-04-04 16:21:05 +02:00
exports . add _custom _profile _fields _to _settings ( ) ;
2017-04-06 02:28:57 +02:00
$ ( "#account-settings-status" ) . hide ( ) ;
2017-06-13 17:57:33 +02:00
2019-11-02 00:06:25 +01:00
const setup _api _key _modal = _ . once ( function ( ) {
2019-08-12 16:43:20 +02:00
$ ( '.account-settings-form' ) . append ( render _settings _api _key _modal ( ) ) ;
$ ( "#api_key_value" ) . text ( "" ) ;
$ ( "#show_api_key" ) . hide ( ) ;
if ( page _params . realm _password _auth _enabled === false ) {
2019-06-07 02:02:50 +02:00
// Skip the password prompt step, since the user doesn't have one.
$ ( "#get_api_key_button" ) . click ( ) ;
2017-06-13 17:57:33 +02:00
}
2018-05-18 15:21:44 +02:00
2019-08-12 16:43:20 +02:00
$ ( "#get_api_key_button" ) . on ( "click" , function ( e ) {
2019-11-02 00:06:25 +01:00
const data = { } ;
2019-08-12 16:43:20 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
data . password = $ ( "#get_api_key_password" ) . val ( ) ;
channel . post ( {
url : '/json/fetch_api_key' ,
data : data ,
success : function ( data ) {
$ ( "#get_api_key_password" ) . val ( "" ) ;
$ ( "#api_key_value" ) . text ( data . api _key ) ;
// The display property on the error bar is set to important
// so instead of making display: none !important we just
// remove it.
$ ( '#api_key_status' ) . remove ( ) ;
$ ( "#password_confirmation" ) . hide ( ) ;
$ ( "#show_api_key" ) . show ( ) ;
} ,
error : function ( xhr ) {
ui _report . error ( i18n . t ( "Error" ) , xhr , $ ( '#api_key_status' ) . expectOne ( ) ) ;
$ ( "#show_api_key" ) . hide ( ) ;
$ ( "#api_key_modal" ) . show ( ) ;
} ,
} ) ;
} ) ;
2018-05-18 15:21:44 +02:00
2019-08-12 16:43:20 +02:00
$ ( "#show_api_key" ) . on ( "click" , "button.regenerate_api_key" , function ( e ) {
channel . post ( {
url : '/json/users/me/api_key/regenerate' ,
success : function ( data ) {
$ ( '#api_key_value' ) . text ( data . api _key ) ;
} ,
error : function ( xhr ) {
$ ( '#user_api_key_error' ) . text ( JSON . parse ( xhr . responseText ) . msg ) . show ( ) ;
} ,
} ) ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2018-05-18 15:21:44 +02:00
} ) ;
2017-06-13 17:57:33 +02:00
2019-08-12 16:43:20 +02:00
$ ( "#download_zuliprc" ) . on ( "click" , function ( ) {
2020-02-01 00:04:23 +01:00
const bot _object = {
user _id : people . my _current _user _id ( ) ,
2020-04-09 22:22:58 +02:00
email : page _params . delivery _email ,
2020-02-01 00:04:23 +01:00
api _key : $ ( "#api_key_value" ) . text ( ) ,
} ;
const data = settings _bots . generate _zuliprc _content ( bot _object ) ;
2019-08-12 16:43:20 +02:00
$ ( this ) . attr ( "href" , settings _bots . encode _zuliprc _as _uri ( data ) ) ;
2017-06-13 17:57:33 +02:00
} ) ;
} ) ;
2019-08-12 16:43:20 +02:00
$ ( '#api_key_button' ) . click ( function ( e ) {
setup _api _key _modal ( ) ;
overlays . open _modal ( 'api_key_modal' ) ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2017-06-13 17:57:33 +02:00
} ) ;
2017-04-06 02:28:57 +02:00
function clear _password _change ( ) {
// Clear the password boxes so that passwords don't linger in the DOM
// for an XSS attacker to find.
2018-01-11 20:28:18 +01:00
$ ( '#old_password, #new_password' ) . val ( '' ) ;
2018-01-24 13:30:27 +01:00
common . password _quality ( '' , $ ( '#pw_strength .bar' ) , $ ( '#new_password' ) ) ;
2017-04-06 02:28:57 +02:00
}
clear _password _change ( ) ;
2018-01-30 08:56:15 +01:00
$ ( "#change_full_name" ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2019-07-15 23:12:31 +02:00
if ( exports . user _can _change _name ( ) ) {
2019-07-10 19:24:39 +02:00
$ ( '#change_full_name_modal' ) . find ( "input[name='full_name']" ) . val ( page _params . full _name ) ;
2018-02-02 15:40:15 +01:00
overlays . open _modal ( 'change_full_name_modal' ) ;
}
2018-01-30 08:56:15 +01:00
} ) ;
2017-12-24 19:00:29 +01:00
$ ( '#change_password' ) . on ( 'click' , function ( e ) {
2017-04-06 02:28:57 +02:00
e . preventDefault ( ) ;
2017-12-24 19:00:29 +01:00
e . stopPropagation ( ) ;
overlays . open _modal ( 'change_password_modal' ) ;
2017-04-06 02:28:57 +02:00
$ ( '#pw_change_controls' ) . show ( ) ;
2017-04-20 08:21:31 +02:00
if ( page _params . realm _password _auth _enabled !== false ) {
2017-04-06 02:28:57 +02:00
// zxcvbn.js is pretty big, and is only needed on password
// change, so load it asynchronously.
2019-06-29 08:50:56 +02:00
require ( [ 'zxcvbn' ] , function ( zxcvbn ) {
window . zxcvbn = zxcvbn ;
2017-04-06 02:28:57 +02:00
$ ( '#pw_strength .bar' ) . removeClass ( "fade" ) ;
} ) ;
}
} ) ;
2018-01-24 13:30:27 +01:00
$ ( '#change_password_modal' ) . find ( '[data-dismiss=modal]' ) . on ( 'click' , function ( ) {
clear _password _change ( ) ;
} ) ;
2018-12-06 20:57:01 +01:00
// If the modal is closed using the 'close' button or the 'Cancel' button
$ ( '.modal' ) . find ( '[data-dismiss=modal]' ) . on ( 'click' , function ( ) {
// Enable mouse events for the background on closing modal
$ ( '.overlay.show' ) . attr ( "style" , null ) ;
} ) ;
2017-12-24 19:00:29 +01:00
$ ( '#change_password_button' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2019-11-02 00:06:25 +01:00
const change _password _error = $ ( '#change_password_modal' ) . find ( ".change_password_info" ) . expectOne ( ) ;
2017-12-24 19:00:29 +01:00
2019-11-02 00:06:25 +01:00
const data = {
2017-12-24 19:00:29 +01:00
old _password : $ ( '#old_password' ) . val ( ) ,
new _password : $ ( '#new_password' ) . val ( ) ,
confirm _password : $ ( '#confirm_password' ) . val ( ) ,
} ;
2019-11-02 00:06:25 +01:00
const new _pw _field = $ ( '#new_password' ) ;
const new _pw = data . new _password ;
2019-03-19 06:10:47 +01:00
if ( new _pw !== '' ) {
2019-11-02 00:06:25 +01:00
const password _ok = common . password _quality ( new _pw , undefined , new _pw _field ) ;
2019-03-19 06:10:47 +01:00
if ( password _ok === undefined ) {
// zxcvbn.js didn't load, for whatever reason.
settings _change _error (
'An internal error occurred; try reloading the page. ' +
'Sorry for the trouble!' ) ;
return ;
} else if ( ! password _ok ) {
settings _change _error ( i18n . t ( 'New password is too weak' ) ) ;
return ;
}
}
2019-11-02 00:06:25 +01:00
const opts = {
2019-07-10 19:17:26 +02:00
success _continuation : function ( ) {
overlays . close _modal ( "change_password_modal" ) ;
2017-12-24 19:00:29 +01:00
} ,
2019-07-10 19:17:26 +02:00
error _msg _element : change _password _error ,
} ;
settings _ui . do _settings _change ( channel . patch , '/json/settings' , data ,
$ ( '#account-settings-status' ) . expectOne ( ) , opts ) ;
clear _password _change ( ) ;
2017-12-24 19:00:29 +01:00
} ) ;
2019-06-27 05:44:25 +02:00
$ ( '#new_password' ) . on ( 'input' , function ( ) {
2019-11-02 00:06:25 +01:00
const field = $ ( '#new_password' ) ;
2017-06-22 22:08:43 +02:00
common . password _quality ( field . val ( ) , $ ( '#pw_strength .bar' ) , field ) ;
2017-04-06 02:28:57 +02:00
} ) ;
2018-01-30 08:56:15 +01:00
$ ( "#change_full_name_button" ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2019-11-02 00:06:25 +01:00
const change _full _name _error = $ ( '#change_full_name_modal' ) . find ( ".change_full_name_info" ) . expectOne ( ) ;
const data = { } ;
2018-01-30 08:56:15 +01:00
data . full _name = $ ( '.full_name_change_container' ) . find ( "input[name='full_name']" ) . val ( ) ;
2019-07-10 19:17:26 +02:00
2019-11-02 00:06:25 +01:00
const opts = {
2019-07-10 19:17:26 +02:00
success _continuation : function ( ) {
overlays . close _modal ( "change_full_name_modal" ) ;
2018-01-30 08:56:15 +01:00
} ,
2019-07-10 19:17:26 +02:00
error _msg _element : change _full _name _error ,
} ;
settings _ui . do _settings _change ( channel . patch , '/json/settings' , data ,
$ ( '#account-settings-status' ) . expectOne ( ) , opts ) ;
2017-04-06 02:28:57 +02:00
} ) ;
$ ( '#change_email_button' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2019-11-02 00:06:25 +01:00
const change _email _error = $ ( '#change_email_modal' ) . find ( ".change_email_info" ) . expectOne ( ) ;
const data = { } ;
2017-04-06 02:28:57 +02:00
data . email = $ ( '.email_change_container' ) . find ( "input[name='email']" ) . val ( ) ;
2019-11-02 00:06:25 +01:00
const opts = {
2019-07-10 19:17:26 +02:00
success _continuation : function ( ) {
if ( page _params . development _environment ) {
2019-11-02 00:06:25 +01:00
const email _msg = render _settings _dev _env _email _access ( ) ;
2019-07-10 19:17:26 +02:00
ui _report . success ( email _msg , $ ( "#dev-account-settings-status" ) . expectOne ( ) , 4000 ) ;
2017-04-06 02:28:57 +02:00
}
2018-01-30 09:07:38 +01:00
overlays . close _modal ( 'change_email_modal' ) ;
2017-04-06 02:28:57 +02:00
} ,
2019-07-10 19:17:26 +02:00
error _msg _element : change _email _error ,
2019-07-16 21:03:25 +02:00
success _msg : i18n . t ( 'Check your email (%s) to confirm the new address.' ) . replace (
"%s" , data . email ) ,
2019-07-10 19:17:26 +02:00
} ;
settings _ui . do _settings _change ( channel . patch , '/json/settings' , data ,
$ ( '#account-settings-status' ) . expectOne ( ) , opts ) ;
2017-04-06 02:28:57 +02:00
} ) ;
$ ( '#change_email' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2018-02-02 16:54:26 +01:00
if ( ! page _params . realm _email _changes _disabled || page _params . is _admin ) {
2018-02-02 15:40:15 +01:00
overlays . open _modal ( 'change_email_modal' ) ;
2019-11-02 00:06:25 +01:00
const email = $ ( '#email_value' ) . text ( ) . trim ( ) ;
2018-02-02 15:40:15 +01:00
$ ( '.email_change_container' ) . find ( "input[name='email']" ) . val ( email ) ;
}
2017-04-06 02:28:57 +02:00
} ) ;
$ ( "#user_deactivate_account_button" ) . on ( 'click' , function ( e ) {
2018-05-23 22:29:00 +02:00
// This click event must not get propagated to parent container otherwise the modal
// will not show up because of a call to `close_active_modal` in `settings.js`.
2017-04-06 02:28:57 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
$ ( "#deactivate_self_modal" ) . modal ( "show" ) ;
} ) ;
2019-07-29 20:27:26 +02:00
$ ( '#account-settings' ) . on ( 'click' , '.custom_user_field .remove_date' , function ( e ) {
2018-06-09 15:28:55 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2019-11-02 00:06:25 +01:00
const field = $ ( e . target ) . closest ( '.custom_user_field' ) . expectOne ( ) ;
const field _id = parseInt ( $ ( field ) . attr ( "data-field-id" ) , 10 ) ;
2018-06-09 15:28:55 +02:00
update _user _custom _profile _fields ( [ field _id ] , channel . del ) ;
} ) ;
2019-07-29 20:27:26 +02:00
$ ( '#account-settings' ) . on ( 'change' , '.custom_user_field_value' , function ( e ) {
2019-11-02 00:06:25 +01:00
const fields = [ ] ;
const value = $ ( this ) . val ( ) ;
const field _id = parseInt ( $ ( e . target ) . closest ( '.custom_user_field' ) . attr ( "data-field-id" ) , 10 ) ;
2018-06-05 12:57:02 +02:00
if ( value ) {
fields . push ( { id : field _id , value : value } ) ;
update _user _custom _profile _fields ( fields , channel . patch ) ;
} else {
fields . push ( field _id ) ;
update _user _custom _profile _fields ( fields , channel . del ) ;
}
2018-02-26 20:42:37 +01:00
} ) ;
2018-12-07 21:21:39 +01:00
$ ( "#do_deactivate_self_button" ) . on ( 'click' , function ( ) {
2018-01-26 08:01:58 +01:00
$ ( "#do_deactivate_self_button .loader" ) . css ( 'display' , 'inline-block' ) ;
$ ( "#do_deactivate_self_button span" ) . hide ( ) ;
$ ( "#do_deactivate_self_button object" ) . on ( "load" , function ( ) {
2019-11-02 00:06:25 +01:00
const doc = this . getSVGDocument ( ) ;
const $svg = $ ( doc ) . find ( "svg" ) ;
2018-01-26 08:01:58 +01:00
$svg . find ( "rect" ) . css ( "fill" , "#000" ) ;
2017-04-06 02:28:57 +02:00
} ) ;
2018-01-26 08:01:58 +01:00
setTimeout ( function ( ) {
channel . del ( {
url : '/json/users/me' ,
success : function ( ) {
$ ( "#deactivate_self_modal" ) . modal ( "hide" ) ;
2018-12-04 02:12:08 +01:00
window . location . href = "/login/" ;
2018-01-26 08:01:58 +01:00
} ,
error : function ( xhr ) {
2019-11-02 00:06:25 +01:00
const error _last _admin = i18n . t ( "Error: Cannot deactivate the only organization administrator." ) ;
const error _last _user = i18n . t ( "Error: Cannot deactivate the only user. You can deactivate the whole organization though in your <a target=\"_blank\" href=\"/#organization/organization-profile\">Organization profile settings</a>." ) ;
let rendered _error _msg ;
2018-08-21 08:14:46 +02:00
if ( xhr . responseJSON . code === "CANNOT_DEACTIVATE_LAST_USER" ) {
if ( xhr . responseJSON . is _last _admin ) {
rendered _error _msg = error _last _admin ;
} else {
rendered _error _msg = error _last _user ;
}
}
2018-01-26 08:01:58 +01:00
$ ( "#deactivate_self_modal" ) . modal ( "hide" ) ;
2018-08-21 08:14:46 +02:00
$ ( "#account-settings-status" ) . addClass ( "alert-error" ) . html ( rendered _error _msg ) . show ( ) ;
2018-01-26 08:01:58 +01:00
} ,
} ) ;
} , 5000 ) ;
2017-04-06 02:28:57 +02:00
} ) ;
2018-11-29 04:29:17 +01:00
$ ( "#show_my_user_profile_modal" ) . on ( 'click' , function ( ) {
2018-08-25 15:27:28 +02:00
overlays . close _overlay ( "settings" ) ;
2020-02-05 14:30:59 +01:00
const user = people . get _by _user _id ( people . my _current _user _id ( ) ) ;
2018-08-25 15:27:28 +02:00
setTimeout ( function ( ) {
2018-11-29 01:46:15 +01:00
popovers . show _user _profile ( user ) ;
2018-08-25 15:27:28 +02:00
} , 100 ) ;
2018-09-02 20:29:37 +02:00
// If user opened the "preview profile" modal from user
// settings, then closing preview profile modal should
// send them back to the settings modal.
$ ( 'body' ) . one ( 'hidden.bs.modal' , '#user-profile-modal' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
popovers . hide _user _profile ( ) ;
setTimeout ( function ( ) {
if ( ! overlays . settings _open ( ) ) {
overlays . open _settings ( ) ;
}
} , 100 ) ;
} ) ;
2018-08-25 15:27:28 +02:00
} ) ;
2017-04-06 02:28:57 +02:00
function upload _avatar ( file _input ) {
2019-11-02 00:06:25 +01:00
const form _data = new FormData ( ) ;
2017-04-06 02:28:57 +02:00
form _data . append ( 'csrfmiddlewaretoken' , csrf _token ) ;
2020-02-08 01:53:49 +01:00
for ( const [ i , file ] of Array . prototype . entries . call ( file _input [ 0 ] . files ) ) {
2018-06-04 21:13:07 +02:00
form _data . append ( 'file-' + i , file ) ;
2020-02-08 01:53:49 +01:00
}
2020-04-02 09:55:16 +02:00
display _avatar _upload _started ( ) ;
2017-07-05 19:15:15 +02:00
channel . post ( {
2017-04-06 02:28:57 +02:00
url : '/json/users/me/avatar' ,
data : form _data ,
cache : false ,
processData : false ,
contentType : false ,
2018-10-18 10:04:43 +02:00
success : function ( ) {
2020-04-02 09:55:16 +02:00
display _avatar _upload _complete ( ) ;
2019-01-13 09:11:41 +01:00
$ ( "#user_avatar_file_input_error" ) . hide ( ) ;
2018-03-02 18:33:20 +01:00
$ ( "#user-avatar-source" ) . hide ( ) ;
2018-10-18 10:04:43 +02:00
// Rest of the work is done via the user_events -> avatar_url event we will get
2018-03-02 18:33:20 +01:00
} ,
2019-01-13 09:11:41 +01:00
error : function ( xhr ) {
2020-04-02 09:55:16 +02:00
display _avatar _upload _complete ( ) ;
2018-03-02 18:33:20 +01:00
if ( page _params . avatar _source === 'G' ) {
$ ( "#user-avatar-source" ) . show ( ) ;
}
2019-11-02 00:06:25 +01:00
const $error = $ ( "#user_avatar_file_input_error" ) ;
2019-01-13 09:11:41 +01:00
$error . text ( JSON . parse ( xhr . responseText ) . msg ) ;
$error . show ( ) ;
2017-04-06 02:28:57 +02:00
} ,
} ) ;
}
avatar . build _user _avatar _widget ( upload _avatar ) ;
2017-04-20 08:13:16 +02:00
if ( page _params . realm _name _changes _disabled ) {
2017-04-06 02:28:57 +02:00
$ ( ".name_change_container" ) . hide ( ) ;
}
} ;
2019-10-25 09:45:13 +02:00
window . settings _account = exports ;