309 lines
11 KiB
JavaScript
309 lines
11 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
// ENiGMA½
|
|
const MenuModule = require('./menu_module.js').MenuModule;
|
|
const ViewController = require('./view_controller.js').ViewController;
|
|
const theme = require('./theme.js');
|
|
const sysValidate = require('./system_view_validate.js');
|
|
const UserProps = require('./user_property.js');
|
|
const { getISOTimestampString } = require('./database.js');
|
|
const EnigAssert = require('./enigma_assert');
|
|
|
|
// deps
|
|
const async = require('async');
|
|
const _ = require('lodash');
|
|
const moment = require('moment');
|
|
|
|
exports.moduleInfo = {
|
|
name: 'User Configuration',
|
|
desc: 'Module for user configuration',
|
|
author: 'NuSkooler',
|
|
};
|
|
|
|
const MciCodeIds = {
|
|
RealName: 1,
|
|
BirthDate: 2,
|
|
Sex: 3,
|
|
Loc: 4,
|
|
Affils: 5,
|
|
Email: 6,
|
|
Web: 7,
|
|
TermHeight: 8,
|
|
Theme: 9,
|
|
Password: 10,
|
|
PassConfirm: 11,
|
|
ThemeInfo: 20,
|
|
ErrorMsg: 21,
|
|
|
|
SaveCancel: 25,
|
|
};
|
|
|
|
exports.getModule = class UserConfigModule extends MenuModule {
|
|
constructor(options) {
|
|
super(options);
|
|
|
|
const self = this;
|
|
|
|
this.menuMethods = {
|
|
//
|
|
// Validation support
|
|
//
|
|
validateEmailAvail: function (data, cb) {
|
|
//
|
|
// If nothing changed, we know it's OK
|
|
//
|
|
if (
|
|
self.client.user.properties[UserProps.EmailAddress].toLowerCase() ===
|
|
data.toLowerCase()
|
|
) {
|
|
return cb(null);
|
|
}
|
|
|
|
// Otherwise we can use the standard system method
|
|
return sysValidate.validateEmailAvail(data, cb);
|
|
},
|
|
|
|
validatePassword: function (data, cb) {
|
|
//
|
|
// Blank is OK - this means we won't be changing it
|
|
//
|
|
if (!data || 0 === data.length) {
|
|
return cb(null);
|
|
}
|
|
|
|
// Otherwise we can use the standard system method
|
|
return sysValidate.validatePasswordSpec(data, cb);
|
|
},
|
|
|
|
validatePassConfirmMatch: function (data, cb) {
|
|
var passwordView = self.getMenuView(MciCodeIds.Password);
|
|
cb(
|
|
passwordView.getData() === data
|
|
? null
|
|
: new Error('Passwords do not match')
|
|
);
|
|
},
|
|
|
|
viewValidationListener: function (err, cb) {
|
|
var errMsgView = self.getMenuView(MciCodeIds.ErrorMsg);
|
|
var newFocusId;
|
|
if (errMsgView) {
|
|
if (err) {
|
|
errMsgView.setText(err.friendlyText);
|
|
|
|
if (err.view.getId() === MciCodeIds.PassConfirm) {
|
|
newFocusId = MciCodeIds.Password;
|
|
var passwordView = self.getMenuView(MciCodeIds.Password);
|
|
passwordView.clearText();
|
|
err.view.clearText();
|
|
}
|
|
} else {
|
|
errMsgView.clearText();
|
|
}
|
|
}
|
|
|
|
return cb(err, newFocusId);
|
|
},
|
|
|
|
//
|
|
// Handlers
|
|
//
|
|
saveChanges: function (formData, extraArgs, cb) {
|
|
EnigAssert(formData.value.password === formData.value.passwordConfirm);
|
|
|
|
// cache a copy of |formData| as changing a theme below can invalidate it
|
|
formData = _.clone(formData);
|
|
|
|
const newProperties = {
|
|
[UserProps.RealName]: formData.value.realName,
|
|
[UserProps.Birthdate]: getISOTimestampString(
|
|
formData.value.birthdate
|
|
),
|
|
[UserProps.Sex]: formData.value.sex,
|
|
[UserProps.Location]: formData.value.location,
|
|
[UserProps.Affiliations]: formData.value.affils,
|
|
[UserProps.EmailAddress]: formData.value.email,
|
|
[UserProps.WebAddress]: formData.value.web,
|
|
[UserProps.TermHeight]: formData.value.termHeight.toString(),
|
|
[UserProps.ThemeId]:
|
|
self.availThemeInfo[formData.value.theme].themeId,
|
|
};
|
|
|
|
// Runtime set theme
|
|
theme.setClientTheme(self.client, newProperties.theme_id);
|
|
|
|
// persist all changes
|
|
self.client.user.persistProperties(newProperties, err => {
|
|
if (err) {
|
|
self.client.log.warn(
|
|
{ error: err.toString() },
|
|
'Failed persisting updated properties'
|
|
);
|
|
// :TODO: warn end user!
|
|
return self.prevMenu(cb);
|
|
}
|
|
|
|
self.client.log.info(
|
|
`User "${self.client.user.username}" updated configuration`
|
|
);
|
|
|
|
//
|
|
// New password if it's not empty
|
|
//
|
|
if (formData.value.password.length > 0) {
|
|
self.client.user.setNewAuthCredentials(
|
|
formData.value.password,
|
|
err => {
|
|
if (err) {
|
|
self.client.log.error(
|
|
{ err: err },
|
|
'Failed storing new authentication credentials'
|
|
);
|
|
} else {
|
|
self.client.log.info(
|
|
`User "${self.client.user.username}" updated authentication credentials`
|
|
);
|
|
}
|
|
return self.prevMenu(cb);
|
|
}
|
|
);
|
|
} else {
|
|
return self.prevMenu(cb);
|
|
}
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
getMenuView(viewId) {
|
|
return this.viewControllers.menu.getView(viewId);
|
|
}
|
|
|
|
mciReady(mciData, cb) {
|
|
super.mciReady(mciData, err => {
|
|
if (err) {
|
|
return cb(err);
|
|
}
|
|
|
|
const self = this;
|
|
const vc = (self.viewControllers.menu = new ViewController({
|
|
client: self.client,
|
|
}));
|
|
let currentThemeIdIndex = 0;
|
|
|
|
async.series(
|
|
[
|
|
function loadFromConfig(callback) {
|
|
vc.loadFromMenuConfig(
|
|
{ callingMenu: self, mciMap: mciData.menu },
|
|
callback
|
|
);
|
|
},
|
|
function prepareAvailableThemes(callback) {
|
|
self.availThemeInfo = _.sortBy(
|
|
[...theme.getAvailableThemes()].map(entry => {
|
|
const theme = entry[1].get();
|
|
return {
|
|
themeId: theme.info.themeId,
|
|
name: theme.info.name,
|
|
author: theme.info.author,
|
|
desc: _.isString(theme.info.desc)
|
|
? theme.info.desc
|
|
: '',
|
|
group: _.isString(theme.info.group)
|
|
? theme.info.group
|
|
: '',
|
|
};
|
|
}),
|
|
'name'
|
|
);
|
|
|
|
currentThemeIdIndex = Math.max(
|
|
0,
|
|
_.findIndex(self.availThemeInfo, function cmp(ti) {
|
|
return (
|
|
ti.themeId ===
|
|
self.client.user.properties[UserProps.ThemeId]
|
|
);
|
|
})
|
|
);
|
|
|
|
callback(null);
|
|
},
|
|
function populateViews(callback) {
|
|
const user = self.client.user;
|
|
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.RealName,
|
|
user.properties[UserProps.RealName]
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.BirthDate,
|
|
moment(user.properties[UserProps.Birthdate]).format(
|
|
'YYYYMMDD'
|
|
)
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.Sex,
|
|
user.properties[UserProps.Sex]
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.Loc,
|
|
user.properties[UserProps.Location]
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.Affils,
|
|
user.properties[UserProps.Affiliations]
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.Email,
|
|
user.properties[UserProps.EmailAddress]
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.Web,
|
|
user.properties[UserProps.WebAddress]
|
|
);
|
|
self.setViewText(
|
|
'menu',
|
|
MciCodeIds.TermHeight,
|
|
user.properties[UserProps.TermHeight].toString()
|
|
);
|
|
|
|
var themeView = self.getMenuView(MciCodeIds.Theme);
|
|
if (themeView) {
|
|
themeView.setItems(_.map(self.availThemeInfo, 'name'));
|
|
themeView.setFocusItemIndex(currentThemeIdIndex);
|
|
}
|
|
|
|
var realNameView = self.getMenuView(MciCodeIds.RealName);
|
|
if (realNameView) {
|
|
realNameView.setFocus(true); // :TODO: HACK! menu.hjson sets focus, but manual population above breaks this. Needs a real fix!
|
|
}
|
|
|
|
callback(null);
|
|
},
|
|
],
|
|
function complete(err) {
|
|
if (err) {
|
|
self.client.log.warn(
|
|
{ error: err.toString() },
|
|
'User configuration failed to init'
|
|
);
|
|
self.prevMenu();
|
|
} else {
|
|
cb(null);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
}
|
|
};
|