2021-03-11 05:43:45 +01:00
import $ from "jquery" ;
2021-02-28 01:18:00 +01:00
import _ from "lodash" ;
import render _settings _api _key _modal from "../templates/settings/api_key_modal.hbs" ;
import render _settings _custom _user _profile _field from "../templates/settings/custom_user_profile_field.hbs" ;
import render _settings _dev _env _email _access from "../templates/settings/dev_env_email_access.hbs" ;
import * as avatar from "./avatar" ;
2021-03-16 23:38:59 +01:00
import * as blueslip from "./blueslip" ;
2021-02-28 01:18:00 +01:00
import * as channel from "./channel" ;
import * as common from "./common" ;
2021-03-25 23:20:18 +01:00
import { csrf _token } from "./csrf" ;
2021-03-25 21:38:40 +01:00
import { i18n } from "./i18n" ;
2021-02-28 01:18:00 +01:00
import * as overlays from "./overlays" ;
2021-03-25 22:35:45 +01:00
import { page _params } from "./page_params" ;
2021-02-28 01:18:00 +01:00
import * as people from "./people" ;
import * as pill _typeahead from "./pill_typeahead" ;
import * as popovers from "./popovers" ;
2021-02-28 01:19:16 +01:00
import * as settings _bots from "./settings_bots" ;
2021-03-21 16:46:13 +01:00
import * as settings _data from "./settings_data" ;
2021-02-28 01:18:00 +01:00
import * as settings _ui from "./settings_ui" ;
import * as setup from "./setup" ;
import * as ui _report from "./ui_report" ;
import * as user _pill from "./user_pill" ;
export function update _email ( new _email ) {
2020-07-15 01:29:15 +02:00
const email _input = $ ( "#email_value" ) ;
2017-04-06 02:28:57 +02:00
if ( email _input ) {
email _input . text ( new _email ) ;
}
2021-02-28 01:18:00 +01:00
}
2017-04-06 02:28:57 +02:00
2021-02-28 01:18:00 +01:00
export function update _full _name ( 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 ) ;
}
2021-02-28 01:18:00 +01:00
}
2018-02-02 01:24:26 +01:00
2021-02-28 01:18:00 +01:00
export function update _name _change _display ( ) {
2021-03-21 16:46:13 +01:00
if ( ! settings _data . user _can _change _name ( ) ) {
2020-07-22 02:59:06 +02:00
$ ( "#full_name" ) . prop ( "disabled" , true ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_name_tooltip" ) . show ( ) ;
} else {
2020-07-22 02:59:06 +02:00
$ ( "#full_name" ) . prop ( "disabled" , false ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_name_tooltip" ) . hide ( ) ;
}
2021-02-28 01:18:00 +01:00
}
2018-03-02 21:44:14 +01:00
2021-02-28 01:18:00 +01:00
export function update _email _change _display ( ) {
2018-03-02 21:44:14 +01:00
if ( page _params . realm _email _changes _disabled && ! page _params . is _admin ) {
2020-07-22 02:59:06 +02:00
$ ( "#change_email .button" ) . prop ( "disabled" , true ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_email_tooltip" ) . show ( ) ;
} else {
2020-07-22 02:59:06 +02:00
$ ( "#change_email .button" ) . prop ( "disabled" , false ) ;
2018-03-02 21:44:14 +01:00
$ ( ".change_email_tooltip" ) . hide ( ) ;
}
2021-02-28 01:18:00 +01:00
}
2018-03-02 21:44:14 +01:00
2021-02-28 01:18:00 +01:00
export function update _avatar _change _display ( ) {
2021-03-21 16:46:13 +01:00
if ( ! settings _data . user _can _change _avatar ( ) ) {
2020-07-22 02:59:06 +02:00
$ ( "#user-avatar-upload-widget .image_upload_button" ) . prop ( "disabled" , true ) ;
$ ( "#user-avatar-upload-widget .image-delete-button .button" ) . prop ( "disabled" , true ) ;
2019-04-23 04:51:04 +02:00
} else {
2020-07-22 02:59:06 +02:00
$ ( "#user-avatar-upload-widget .image_upload_button" ) . prop ( "disabled" , false ) ;
$ ( "#user-avatar-upload-widget .image-delete-button .button" ) . prop ( "disabled" , false ) ;
2019-04-23 04:51:04 +02:00
}
2021-02-28 01:18:00 +01:00
}
2019-04-23 04:51:04 +02:00
2020-04-02 09:55:16 +02:00
function display _avatar _upload _complete ( ) {
2020-07-15 01:29:15 +02:00
$ ( "#user-avatar-upload-widget .upload-spinner-background" ) . css ( { visibility : "hidden" } ) ;
2020-07-15 21:20:36 +02:00
$ ( "#user-avatar-upload-widget .image-upload-text" ) . show ( ) ;
2020-07-15 20:58:34 +02:00
$ ( "#user-avatar-upload-widget .image-delete-button" ) . show ( ) ;
2020-04-02 09:55:16 +02:00
}
function display _avatar _upload _started ( ) {
$ ( "#user-avatar-source" ) . hide ( ) ;
2020-07-15 01:29:15 +02:00
$ ( "#user-avatar-upload-widget .upload-spinner-background" ) . css ( { visibility : "visible" } ) ;
2020-07-15 21:20:36 +02:00
$ ( "#user-avatar-upload-widget .image-upload-text" ) . hide ( ) ;
2020-07-15 20:58:34 +02:00
$ ( "#user-avatar-upload-widget .image-delete-button" ) . hide ( ) ;
2020-04-02 09:55:16 +02:00
}
2017-04-06 02:28:57 +02:00
function settings _change _error ( message , xhr ) {
2020-07-15 01:29:15 +02:00
ui _report . error ( message , xhr , $ ( "#account-settings-status" ) . expectOne ( ) ) ;
2017-04-06 02:28:57 +02:00
}
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 ;
}
2020-07-15 00:34:28 +02:00
const spinner _element = $ (
2021-02-03 23:23:32 +01:00
` .custom_user_field[data-field-id=" ${ CSS . escape ( field _id ) } "] .custom-field-status ` ,
2020-07-15 00:34:28 +02:00
) . expectOne ( ) ;
settings _ui . do _settings _change (
method ,
"/json/users/me/profile_data" ,
{ data : JSON . stringify ( [ field ] ) } ,
spinner _element ,
) ;
2019-07-10 22:18:45 +02:00
}
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
}
2021-02-28 01:18:00 +01:00
export function append _custom _profile _fields ( 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" ] ,
2021-03-24 11:41:29 +01:00
[ all _field _types . SELECT . id , "select" ] ,
2020-02-12 07:19:14 +01:00
[ 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
2021-01-22 22:29:08 +01:00
for ( const field of all _custom _fields ) {
2019-11-02 00:06:25 +01:00
let field _value = people . get _custom _profile _data ( user _id , field . id ) ;
2021-03-24 11:41:29 +01:00
const is _select _field = field . type === all _field _types . SELECT . id ;
2019-11-02 00:06:25 +01:00
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 : "" } ;
}
2021-03-24 11:41:29 +01:00
if ( is _select _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 ( {
2020-07-20 22:18:43 +02:00
field ,
2020-02-12 07:19:14 +01:00
field _type : all _field _template _types . get ( field . type ) ,
2020-07-20 22:18:43 +02:00
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 ,
2021-03-24 11:41:29 +01:00
is _select _field ,
2020-07-20 22:18:43 +02:00
field _choices ,
2018-10-24 16:32:30 +02:00
} ) ;
$ ( element _id ) . append ( html ) ;
2021-01-22 22:29:08 +01:00
}
2021-02-28 01:18:00 +01:00
}
2018-10-24 16:32:30 +02:00
2021-02-28 01:18:00 +01:00
export function initialize _custom _date _type _fields ( element _id ) {
2018-10-22 10:20:33 +02:00
$ ( element _id ) . find ( ".custom_user_field .datepicker" ) . flatpickr ( {
altInput : true ,
2020-06-07 06:16:34 +02:00
altFormat : "F j, Y" ,
allowInput : true ,
} ) ;
2019-07-29 20:54:30 +02:00
2020-07-15 00:34:28 +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
2020-07-15 00:34:28 +02:00
$ ( element _id )
. find ( ".custom_user_field .remove_date" )
. on ( "click" , function ( ) {
$ ( this ) . parent ( ) . find ( ".custom_user_field_value" ) . val ( "" ) ;
} ) ;
2021-02-28 01:18:00 +01:00
}
2018-10-22 10:20:33 +02:00
2021-02-28 01:18:00 +01:00
export function initialize _custom _user _type _fields (
2020-07-15 00:34:28 +02:00
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
}
2021-01-22 22:29:08 +01:00
for ( const field of page _params . custom _profile _fields ) {
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 ) ) {
2020-07-15 00:34:28 +02:00
const pill _container = $ ( element _id )
2021-02-03 23:23:32 +01:00
. find ( ` .custom_user_field[data-field-id=" ${ CSS . escape ( field . id ) } "] .pill-container ` )
2020-07-15 00:34:28 +02:00
. 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 ) {
2021-01-22 22:29:08 +01:00
for ( const pill _user _id of field _value ) {
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 ) ;
2021-01-22 22:29:08 +01:00
}
2018-10-23 19:46:12 +02:00
}
}
2018-12-31 07:45:33 +01:00
2018-10-23 19:46:12 +02:00
if ( is _editable ) {
2020-07-15 01:29:15 +02:00
const input = pill _container . children ( ".input" ) ;
2018-10-23 19:46:12 +02:00
if ( set _handler _on _update ) {
2020-07-18 14:05:48 +02:00
const opts = { update _func : update _custom _user _field } ;
2020-07-24 18:04:15 +02:00
pill _typeahead . set _up ( input , pills , opts ) ;
2020-07-02 01:45:54 +02:00
pills . onPillRemove ( ( ) => {
2018-10-23 19:46:12 +02:00
update _custom _user _field ( ) ;
} ) ;
} else {
2020-07-24 18:04:15 +02:00
pill _typeahead . set _up ( input , pills , { } ) ;
2018-10-23 19:46:12 +02:00
}
}
2020-01-16 20:37:29 +01:00
user _pills . set ( field . id , pills ) ;
2018-10-23 19:46:12 +02:00
}
2021-01-22 22:29:08 +01:00
}
2019-04-07 17:29:53 +02:00
2018-10-23 19:46:12 +02:00
return user _pills ;
2021-02-28 01:18:00 +01:00
}
2018-10-23 19:46:12 +02:00
2021-02-28 01:18:00 +01:00
export function add _custom _profile _fields _to _settings ( ) {
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 ( ) ;
}
2021-02-28 01:18:00 +01:00
append _custom _profile _fields ( element _id , people . my _current _user _id ( ) ) ;
initialize _custom _user _type _fields ( element _id , people . my _current _user _id ( ) , true , true ) ;
initialize _custom _date _type _fields ( element _id ) ;
}
2017-04-06 02:28:57 +02:00
2021-02-28 01:18:00 +01:00
export function set _up ( ) {
2018-02-26 20:09:07 +01:00
// Add custom profile fields elements to user account settings.
2021-02-28 01:18:00 +01:00
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
2020-07-02 01:45:54 +02:00
const setup _api _key _modal = _ . once ( ( ) => {
2020-06-05 20:34:18 +02:00
function request _api _key ( data ) {
2019-08-12 16:43:20 +02:00
channel . post ( {
2020-07-15 01:29:15 +02:00
url : "/json/fetch_api_key" ,
2020-07-20 22:18:43 +02:00
data ,
success ( data ) {
2019-08-12 16:43:20 +02:00
$ ( "#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.
2020-07-15 01:29:15 +02:00
$ ( "#api_key_status" ) . remove ( ) ;
2019-08-12 16:43:20 +02:00
$ ( "#password_confirmation" ) . hide ( ) ;
$ ( "#show_api_key" ) . show ( ) ;
} ,
2020-07-20 22:18:43 +02:00
error ( xhr ) {
2020-07-15 01:29:15 +02:00
ui _report . error ( i18n . t ( "Error" ) , xhr , $ ( "#api_key_status" ) . expectOne ( ) ) ;
2019-08-12 16:43:20 +02:00
$ ( "#show_api_key" ) . hide ( ) ;
$ ( "#api_key_modal" ) . show ( ) ;
} ,
} ) ;
2020-06-05 20:34:18 +02:00
}
2020-07-15 01:29:15 +02:00
$ ( ".account-settings-form" ) . append ( render _settings _api _key _modal ( ) ) ;
2020-06-05 20:34:18 +02:00
$ ( "#api_key_value" ) . text ( "" ) ;
$ ( "#show_api_key" ) . hide ( ) ;
if ( page _params . realm _password _auth _enabled === false ) {
// Skip the password prompt step, since the user doesn't have one.
request _api _key ( { } ) ;
} else {
2020-07-02 01:45:54 +02:00
$ ( "#get_api_key_button" ) . on ( "click" , ( e ) => {
2020-06-05 20:34:18 +02:00
const data = { } ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
data . password = $ ( "#get_api_key_password" ) . val ( ) ;
request _api _key ( data ) ;
} ) ;
}
2018-05-18 15:21:44 +02:00
2020-07-02 01:45:54 +02:00
$ ( "#show_api_key" ) . on ( "click" , "button.regenerate_api_key" , ( e ) => {
2019-08-12 16:43:20 +02:00
channel . post ( {
2020-07-15 01:29:15 +02:00
url : "/json/users/me/api_key/regenerate" ,
2020-07-20 22:18:43 +02:00
success ( data ) {
2020-07-15 01:29:15 +02:00
$ ( "#api_key_value" ) . text ( data . api _key ) ;
2019-08-12 16:43:20 +02:00
} ,
2020-07-20 22:18:43 +02:00
error ( xhr ) {
2020-07-15 01:29:15 +02:00
$ ( "#user_api_key_error" ) . text ( JSON . parse ( xhr . responseText ) . msg ) . show ( ) ;
2019-08-12 16:43:20 +02:00
} ,
} ) ;
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
} ) ;
} ) ;
2020-07-20 21:26:58 +02:00
$ ( "#api_key_button" ) . on ( "click" , ( e ) => {
2019-08-12 16:43:20 +02:00
setup _api _key _modal ( ) ;
2020-07-15 01:29:15 +02:00
overlays . open _modal ( "#api_key_modal" ) ;
2019-08-12 16:43:20 +02:00
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.
2020-07-15 01:29:15 +02:00
$ ( "#old_password, #new_password" ) . val ( "" ) ;
common . password _quality ( "" , $ ( "#pw_strength .bar" ) , $ ( "#new_password" ) ) ;
2017-04-06 02:28:57 +02:00
}
clear _password _change ( ) ;
2020-07-15 01:29:15 +02:00
$ ( "#change_full_name" ) . on ( "click" , ( e ) => {
2018-01-30 08:56:15 +01:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2021-03-21 16:46:13 +01:00
if ( settings _data . user _can _change _name ( ) ) {
2020-07-15 01:29:15 +02:00
$ ( "#change_full_name_modal" ) . find ( "input[name='full_name']" ) . val ( page _params . full _name ) ;
overlays . open _modal ( "#change_full_name_modal" ) ;
2018-02-02 15:40:15 +01:00
}
2018-01-30 08:56:15 +01:00
} ) ;
2021-02-28 21:16:38 +01:00
$ ( "#change_password" ) . on ( "click" , async ( e ) => {
2017-04-06 02:28:57 +02:00
e . preventDefault ( ) ;
2017-12-24 19:00:29 +01:00
e . stopPropagation ( ) ;
2020-07-15 01:29:15 +02:00
overlays . open _modal ( "#change_password_modal" ) ;
$ ( "#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.
2021-02-28 21:16:38 +01:00
const { default : zxcvbn } = await import ( "zxcvbn" ) ;
window . zxcvbn = zxcvbn ;
$ ( "#pw_strength .bar" ) . removeClass ( "fade" ) ;
2017-04-06 02:28:57 +02:00
}
} ) ;
2020-07-15 00:34:28 +02:00
$ ( "#change_password_modal" )
. find ( "[data-dismiss=modal]" )
. on ( "click" , ( ) => {
clear _password _change ( ) ;
} ) ;
2018-01-24 13:30:27 +01:00
2020-07-15 01:29:15 +02:00
$ ( "#change_password_button" ) . on ( "click" , ( e ) => {
2017-12-24 19:00:29 +01:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2020-07-15 00:34:28 +02: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 = {
2020-07-15 01:29:15 +02:00
old _password : $ ( "#old_password" ) . val ( ) ,
new _password : $ ( "#new_password" ) . val ( ) ,
confirm _password : $ ( "#confirm_password" ) . val ( ) ,
2017-12-24 19:00:29 +01:00
} ;
2020-07-15 01:29:15 +02:00
const new _pw _field = $ ( "#new_password" ) ;
2019-11-02 00:06:25 +01:00
const new _pw = data . new _password ;
2020-07-15 01:29:15 +02: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 (
2020-07-15 01:29:15 +02:00
"An internal error occurred; try reloading the page. " +
2020-07-15 00:34:28 +02:00
"Sorry for the trouble!" ,
) ;
2019-03-19 06:10:47 +01:00
return ;
} else if ( ! password _ok ) {
2020-07-15 01:29:15 +02:00
settings _change _error ( i18n . t ( "New password is too weak" ) ) ;
2019-03-19 06:10:47 +01:00
return ;
}
}
2020-09-24 05:23:09 +02:00
setup . set _password _change _in _progress ( true ) ;
2019-11-02 00:06:25 +01:00
const opts = {
2020-07-20 22:18:43 +02:00
success _continuation ( ) {
2020-09-24 05:23:09 +02:00
setup . set _password _change _in _progress ( false ) ;
2020-05-09 15:45:54 +02:00
overlays . close _modal ( "#change_password_modal" ) ;
2017-12-24 19:00:29 +01:00
} ,
2020-08-25 18:37:48 +02:00
error _continuation ( ) {
2020-09-24 05:23:09 +02:00
setup . set _password _change _in _progress ( false ) ;
2020-08-25 18:37:48 +02:00
} ,
2019-07-10 19:17:26 +02:00
error _msg _element : change _password _error ,
} ;
2020-07-15 00:34:28 +02:00
settings _ui . do _settings _change (
channel . patch ,
"/json/settings" ,
data ,
$ ( "#account-settings-status" ) . expectOne ( ) ,
opts ,
) ;
2019-07-10 19:17:26 +02:00
clear _password _change ( ) ;
2017-12-24 19:00:29 +01:00
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#new_password" ) . on ( "input" , ( ) => {
const field = $ ( "#new_password" ) ;
common . password _quality ( field . val ( ) , $ ( "#pw_strength .bar" ) , field ) ;
2017-04-06 02:28:57 +02:00
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#change_full_name_button" ) . on ( "click" , ( e ) => {
2018-01-30 08:56:15 +01:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2020-07-15 00:34:28 +02:00
const change _full _name _error = $ ( "#change_full_name_modal" )
. find ( ".change_full_name_info" )
. expectOne ( ) ;
2019-11-02 00:06:25 +01:00
const data = { } ;
2018-01-30 08:56:15 +01:00
2020-07-15 01:29:15 +02: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 = {
2020-07-20 22:18:43 +02:00
success _continuation ( ) {
2020-05-09 15:45:54 +02:00
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 ,
} ;
2020-07-15 00:34:28 +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
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#change_email_button" ) . on ( "click" , ( e ) => {
2017-04-06 02:28:57 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2020-07-15 01:29:15 +02:00
const change _email _error = $ ( "#change_email_modal" ) . find ( ".change_email_info" ) . expectOne ( ) ;
2019-11-02 00:06:25 +01:00
const data = { } ;
2020-07-15 01:29:15 +02:00
data . email = $ ( ".email_change_container" ) . find ( "input[name='email']" ) . val ( ) ;
2017-04-06 02:28:57 +02:00
2019-11-02 00:06:25 +01:00
const opts = {
2020-07-20 22:18:43 +02:00
success _continuation ( ) {
2019-07-10 19:17:26 +02:00
if ( page _params . development _environment ) {
2019-11-02 00:06:25 +01:00
const email _msg = render _settings _dev _env _email _access ( ) ;
2020-07-15 00:34:28 +02:00
ui _report . success (
email _msg ,
$ ( "#dev-account-settings-status" ) . expectOne ( ) ,
4000 ,
) ;
2017-04-06 02:28:57 +02:00
}
2020-07-15 01:29:15 +02: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 ,
2020-07-15 00:34:28 +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
} ;
2020-07-15 00:34:28 +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
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#change_email" ) . on ( "click" , ( e ) => {
2017-04-06 02:28:57 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2018-02-02 16:54:26 +01:00
if ( ! page _params . realm _email _changes _disabled || page _params . is _admin ) {
2020-07-15 01:29:15 +02:00
overlays . open _modal ( "#change_email_modal" ) ;
const email = $ ( "#email_value" ) . text ( ) . trim ( ) ;
$ ( ".email_change_container" ) . find ( "input[name='email']" ) . val ( email ) ;
2018-02-02 15:40:15 +01:00
}
2017-04-06 02:28:57 +02:00
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#user_deactivate_account_button" ) . on ( "click" , ( 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" ) ;
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#account-settings" ) . on ( "click" , ".custom_user_field .remove_date" , ( e ) => {
2018-06-09 15:28:55 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2020-07-15 01:29:15 +02:00
const field = $ ( e . target ) . closest ( ".custom_user_field" ) . expectOne ( ) ;
2020-10-07 09:17:30 +02:00
const field _id = Number . parseInt ( $ ( field ) . attr ( "data-field-id" ) , 10 ) ;
2018-06-09 15:28:55 +02:00
update _user _custom _profile _fields ( [ field _id ] , channel . del ) ;
} ) ;
2020-07-15 01:29:15 +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 ( ) ;
2020-10-07 09:17:30 +02:00
const field _id = Number . parseInt (
2020-07-15 00:34:28 +02:00
$ ( e . target ) . closest ( ".custom_user_field" ) . attr ( "data-field-id" ) ,
10 ,
) ;
2018-06-05 12:57:02 +02:00
if ( value ) {
2020-07-20 22:18:43 +02:00
fields . push ( { id : field _id , value } ) ;
2018-06-05 12:57:02 +02:00
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
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#do_deactivate_self_button" ) . on ( "click" , ( ) => {
$ ( "#do_deactivate_self_button .loader" ) . css ( "display" , "inline-block" ) ;
2018-01-26 08:01:58 +01:00
$ ( "#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
2020-07-02 01:45:54 +02:00
setTimeout ( ( ) => {
2018-01-26 08:01:58 +01:00
channel . del ( {
2020-07-15 01:29:15 +02:00
url : "/json/users/me" ,
2020-07-20 22:18:43 +02:00
success ( ) {
2018-01-26 08:01:58 +01:00
$ ( "#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
} ,
2020-07-20 22:18:43 +02:00
error ( xhr ) {
2020-08-22 00:28:49 +02:00
const error _last _owner = i18n . t (
"Error: Cannot deactivate the only organization owner." ,
2020-07-15 00:34:28 +02:00
) ;
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>.' ,
) ;
2019-11-02 00:06:25 +01:00
let rendered _error _msg ;
2018-08-21 08:14:46 +02:00
if ( xhr . responseJSON . code === "CANNOT_DEACTIVATE_LAST_USER" ) {
2020-08-22 00:28:49 +02:00
if ( xhr . responseJSON . is _last _owner ) {
rendered _error _msg = error _last _owner ;
2018-08-21 08:14:46 +02:00
} else {
rendered _error _msg = error _last _user ;
}
}
2018-01-26 08:01:58 +01:00
$ ( "#deactivate_self_modal" ) . modal ( "hide" ) ;
2020-07-15 00:34:28 +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
} ) ;
2020-07-15 01:29:15 +02:00
$ ( "#show_my_user_profile_modal" ) . on ( "click" , ( ) => {
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 ( ) ) ;
2020-07-02 01:45:54 +02:00
setTimeout ( ( ) => {
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.
2020-07-15 01:29:15 +02:00
$ ( "body" ) . one ( "hidden.bs.modal" , "#user-profile-modal" , ( e ) => {
2018-09-02 20:29:37 +02:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
popovers . hide _user _profile ( ) ;
2020-07-02 01:45:54 +02:00
setTimeout ( ( ) => {
2018-09-02 20:29:37 +02:00
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
2020-07-15 01:29:15 +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 ) ) {
2020-07-15 01:29:15 +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 ( {
2020-07-15 01:29:15 +02:00
url : "/json/users/me/avatar" ,
2017-04-06 02:28:57 +02:00
data : form _data ,
cache : false ,
processData : false ,
contentType : false ,
2020-07-20 22:18:43 +02:00
success ( ) {
2020-04-02 09:55:16 +02:00
display _avatar _upload _complete ( ) ;
2020-06-15 08:28:41 +02:00
$ ( "#user-avatar-upload-widget .image_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
} ,
2020-07-20 22:18:43 +02:00
error ( xhr ) {
2020-04-02 09:55:16 +02:00
display _avatar _upload _complete ( ) ;
2020-07-15 01:29:15 +02:00
if ( page _params . avatar _source === "G" ) {
2018-03-02 18:33:20 +01:00
$ ( "#user-avatar-source" ) . show ( ) ;
}
2020-06-15 08:28:41 +02:00
const $error = $ ( "#user-avatar-upload-widget .image_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 ( ) ;
}
2021-02-28 01:18:00 +01:00
}