You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/settings/inner.js

1577 lines
59 KiB
JavaScript

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

define([
'jquery',
'/common/toolbar3.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/common-util.js',
'/common/common-hash.js',
'/customize/messages.js',
'/common/hyperscript.js',
'/customize/credential.js',
'/customize/application_config.js',
'/api/config',
'/settings/make-backup.js',
'/common/common-feedback.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/settings/app-settings.less',
], function (
$,
Toolbar,
nThen,
SFCommon,
UI,
UIElements,
Util,
Hash,
Messages,
h,
Cred,
AppConfig,
ApiConfig,
Backup,
Feedback
)
{
var saveAs = window.saveAs;
var APP = window.APP = {};
var common;
var metadataMgr;
var privateData;
var sframeChan;
var categories = {
'account': [
'cp-settings-info-block',
'cp-settings-displayname',
'cp-settings-language-selector',
'cp-settings-logout-everywhere',
'cp-settings-autostore',
'cp-settings-userfeedback',
'cp-settings-change-password',
'cp-settings-migrate',
'cp-settings-backup',
'cp-settings-delete'
],
'creation': [
'cp-settings-creation-owned',
'cp-settings-creation-expire',
'cp-settings-creation-skip',
'cp-settings-creation-template'
],
'drive': [
'cp-settings-drive-duplicate',
'cp-settings-resettips',
'cp-settings-thumbnails',
'cp-settings-drive-backup',
'cp-settings-drive-import-local',
'cp-settings-drive-reset'
],
'cursor': [
'cp-settings-cursor-color',
'cp-settings-cursor-share',
'cp-settings-cursor-show',
],
'pad': [
'cp-settings-pad-width',
'cp-settings-pad-spellcheck',
],
'code': [
'cp-settings-code-indent-unit',
'cp-settings-code-indent-type',
'cp-settings-code-font-size',
],
'subscription': {
onClick: function () {
var urls = common.getMetadataMgr().getPrivateData().accounts;
window.open(urls.upgradeURL);
Feedback.send('SUBSCRIPTION_BUTTON');
}
}
};
if (!AppConfig.displayCreationScreen) {
delete categories.creation;
}
if (AppConfig.disableFeedback) {
var feedbackIdx = categories.account.indexOf('cp-settings-userfeedback');
categories.account.splice(feedbackIdx, 1);
}
if (AppConfig.disableProfile) {
var displaynameIdx = categories.account.indexOf('cp-settings-displayname');
categories.account.splice(displaynameIdx, 1);
}
if (!ApiConfig.allowSubscriptions) {
delete categories.subscription;
}
var create = {};
// Account settings
create['info-block'] = function () {
var $div = $('<div>', {'class': 'cp-settings-info-block'});
var $account = $('<div>', {'class': 'cp-sidebarlayout-element'}).appendTo($div);
var accountName = privateData.accountName;
var $label = $('<span>', {'class': 'label'}).text(Messages.user_accountName);
var $name = $('<span>').text(accountName || '');
if (!accountName) {
$label.text('');
$name.text(Messages.settings_anonymous);
}
$account.append($label).append($name);
var publicKey = privateData.edPublic;
if (publicKey) {
var $key = $('<div>', {'class': 'cp-sidebarlayout-element'}).appendTo($div);
var userHref = Hash.getUserHrefFromKeys(privateData.origin, accountName, publicKey);
var $pubLabel = $('<span>', {'class': 'label'})
.text(Messages.settings_publicSigningKey);
$key.append($pubLabel).append(UI.dialog.selectable(userHref));
}
return $div;
};
// Create the block containing the display name field
create['displayname'] = function () {
var $div = $('<div>', {'class': 'cp-settings-displayname cp-sidebarlayout-element'});
$('<label>', {'for' : 'cp-settings-displayname'}).text(Messages.user_displayName).appendTo($div);
var $inputBlock = $('<div>', {'class': 'cp-sidebarlayout-input-block'}).appendTo($div);
var $input = $('<input>', {
'type': 'text',
'id': 'cp-settings-displayname',
'placeholder': Messages.anonymous}).appendTo($inputBlock);
var $save = $('<button>', {'class': 'btn btn-primary'}).text(Messages.settings_save).appendTo($inputBlock);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide().appendTo($div);
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide().appendTo($div);
var displayName = metadataMgr.getUserData().name || '';
$input.val(displayName);
// When the display name is changed (enter or button clicked)
var todo = function () {
displayName = $input.val();
if (displayName === metadataMgr.getUserData().name) { return; }
$spinner.show();
common.setDisplayName(displayName, function () {
$spinner.hide();
$ok.show();
});
};
$input.on('keyup', function (e) {
if ($input.val() !== displayName) { $ok.hide(); }
if (e.which === 13) { todo(); }
});
$save.click(todo);
// On remote change
var onChange = function () {
if (metadataMgr.getUserData().name !== $input.val()) {
$input.val(metadataMgr.getUserData().name);
$input.focusout();
}
};
metadataMgr.onChange(onChange);
return $div;
};
create['language-selector'] = function () {
var $div = $('<div>', {'class': 'cp-settings-language-selector cp-sidebarlayout-element'});
$('<label>').text(Messages.language).appendTo($div);
var $b = common.createLanguageSelector($div);
$b.find('button').addClass('btn btn-secondary');
return $div;
};
create['logout-everywhere'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-logout-everywhere cp-sidebarlayout-element'});
$('<label>').text(Messages.settings_logoutEverywhereTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_logoutEverywhere).appendTo($div);
var $button = $('<button>', {
id: 'cp-settings-logout-everywhere',
'class': 'btn btn-primary'
}).text(Messages.settings_logoutEverywhereButton)
.appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide().appendTo($div);
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide().appendTo($div);
$button.click(function () {
UI.confirm(Messages.settings_logoutEverywhereConfirm, function (yes) {
if (!yes) { return; }
$spinner.show();
$ok.hide();
Feedback.send('LOGOUT_EVERYWHERE');
sframeChan.query('Q_SETTINGS_LOGOUT', null, function () {
$spinner.hide();
$ok.show();
window.setTimeout(function () {
$ok.fadeOut(1500);
}, 2500);
});
});
});
return $div;
};
create['autostore'] = function () {
var $div = $('<div>', { 'class': 'cp-settings-autostore cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_autostoreTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append(Messages.settings_autostoreHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var opt1 = UI.createRadio('cp-settings-autostore', 'cp-settings-autostore-no',
Messages.settings_autostoreNo, false, {
input: { value: -1 },
label: { class: 'noTitle' }
});
var opt2 = UI.createRadio('cp-settings-autostore', 'cp-settings-autostore-maybe',
Messages.settings_autostoreMaybe, true, {
input: { value: 0 },
label: { class: 'noTitle' }
});
var opt3 = UI.createRadio('cp-settings-autostore', 'cp-settings-autostore-yes',
Messages.settings_autostoreYes, false, {
input: { value: 1 },
label: { class: 'noTitle' }
});
var $div2 = $(h('div.cp-settings-autostore-radio', [
opt3,
opt2,
opt1
])).appendTo($div);
$div.find('input[type="radio"]').on('change', function () {
$spinner.show();
$ok.hide();
var val = $('input:radio[name="cp-settings-autostore"]:checked').val();
val = Number(val) || 0;
common.setAttribute(['general', 'autostore'], val, function () {
$spinner.hide();
$ok.show();
});
});
$ok.hide().appendTo($div2);
$spinner.hide().appendTo($div2);
common.getAttribute(['general', 'autostore'], function (err, val) {
if (val === 1) { return void $('#cp-settings-autostore-yes').prop('checked', true); }
if (val === -1) { return void $('#cp-settings-autostore-no').prop('checked', true); }
$('#cp-settings-autostore-maybe').prop('checked', true);
});
return $div;
};
create['userfeedback'] = function () {
var $div = $('<div>', { 'class': 'cp-settings-userfeedback cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_userFeedbackTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append(Messages.settings_userFeedbackHint1)
.append(Messages.settings_userFeedbackHint2).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-userfeedback',
Messages.settings_userFeedback,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked') || false;
common.setAttribute(['general', 'allowUserFeedback'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
if (privateData.feedbackAllowed) {
$checkbox[0].checked = true;
}
return $div;
};
create['delete'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-delete cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_deleteTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append(Messages.settings_deleteHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $button = $('<button>', {'id': 'cp-settings-delete', 'class': 'btn btn-danger'})
.text(Messages.settings_deleteButton).appendTo($div);
$button.click(function () {
$spinner.show();
UI.confirm(Messages.settings_deleteConfirm, function (yes) {
if (!yes) { return; }
sframeChan.query("Q_SETTINGS_DELETE_ACCOUNT", null, function (err, data) {
// Owned drive
if (data.state === true) {
sframeChan.query('Q_SETTINGS_LOGOUT', null, function () {});
UI.alert(Messages.settings_deleted, function () {
common.gotoURL('/');
});
$ok.show();
$spinner.hide();
return;
}
// Not owned drive
var msg = h('div.cp-app-settings-delete-alert', [
h('p', Messages.settings_deleteModal),
h('pre', JSON.stringify(data, 0, 2))
]);
UI.alert(msg);
$spinner.hide();
});
});
// TODO
/*
UI.confirm("Are you sure?", function (yes) {
// Logout everywhere
// Disconnect other tabs
// Remove owned pads
// Remove owned drive
// Remove pinstore
// Alert: "Account deleted", press OK to be redirected to the home page
$spinner.hide();
});*/
});
$spinner.hide().appendTo($div);
$ok.hide().appendTo($div);
return $div;
};
create['change-password'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-change-password cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_changePasswordTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append(Messages.settings_changePasswordHint).appendTo($div);
// var publicKey = privateData.edPublic;
var form = h('div', [
UI.passwordInput({
id: 'cp-settings-change-password-current',
placeholder: Messages.settings_changePasswordCurrent
}, true),
h('br'),
UI.passwordInput({
id: 'cp-settings-change-password-new',
placeholder: Messages.settings_changePasswordNew
}, true),
UI.passwordInput({
id: 'cp-settings-change-password-new2',
placeholder: Messages.settings_changePasswordNewConfirm
}, true),
h('button.btn.btn-primary', Messages.settings_changePasswordButton)
]);
$(form).appendTo($div);
var updateBlock = function (data, cb) {
sframeChan.query('Q_CHANGE_USER_PASSWORD', data, function (err, obj) {
if (err || obj.error) { return void cb ({error: err || obj.error}); }
cb (obj);
});
};
var todo = function () {
var oldPassword = $(form).find('#cp-settings-change-password-current').val();
var newPassword = $(form).find('#cp-settings-change-password-new').val();
var newPasswordConfirm = $(form).find('#cp-settings-change-password-new2').val();
/* basic validation */
if (!Cred.isLongEnoughPassword(newPassword)) {
var warning = Messages._getKey('register_passwordTooShort', [
Cred.MINIMUM_PASSWORD_LENGTH
]);
return void UI.alert(warning);
}
if (newPassword !== newPasswordConfirm) {
UI.alert(Messages.register_passwordsDontMatch);
return;
}
if (oldPassword === newPassword) {
return void UI.alert(Messages.settings_changePasswordNewPasswordSameAsOld);
}
UI.confirm(Messages.settings_changePasswordConfirm,
function (yes) {
if (!yes) { return; }
UI.addLoadingScreen({
hideTips: true,
loadingText: Messages.settings_changePasswordPending,
});
updateBlock({
password: oldPassword,
newPassword: newPassword
}, function (obj) {
UI.removeLoadingScreen();
if (obj && obj.error) {
// TODO
UI.alert(Messages.settings_changePasswordError);
}
});
}, {
ok: Messages.register_writtenPassword,
cancel: Messages.register_cancel,
cancelClass: 'safe',
okClass: 'danger',
reverseOrder: true,
done: function ($dialog) {
$dialog.find('> div').addClass('half');
},
}, true);
};
$(form).find('button').click(function () {
todo();
});
$(form).find('input').keydown(function (e) {
// Save on Enter
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
todo();
}
});
return $div;
};
create['migrate'] = function () {
if (privateData.isDriveOwned) { return; }
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-migrate cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_ownDriveTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append(Messages.settings_ownDriveHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var form = h('div', [
UI.passwordInput({
id: 'cp-settings-migrate-password',
placeholder: Messages.settings_changePasswordCurrent
}, true),
h('button.btn.btn-primary', Messages.settings_ownDriveButton)
]);
$(form).appendTo($div);
var todo = function () {
var password = $(form).find('#cp-settings-migrate-password').val();
if (!password) { return; }
$spinner.show();
UI.confirm(Messages.settings_ownDriveConfirm, function (yes) {
if (!yes) { return; }
var data = {
password: password,
newPassword: password
};
UI.addLoadingScreen({
hideTips: true,
loadingText: Messages.settings_ownDrivePending,
});
sframeChan.query('Q_CHANGE_USER_PASSWORD', data, function (err, obj) {
UI.removeLoadingScreen();
if (err || obj.error) { return UI.alert(Messages.settings_changePasswordError); }
$ok.show();
$spinner.hide();
});
});
};
$(form).find('button').click(function () {
todo();
});
$(form).find('input').keydown(function (e) {
// Save on Enter
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
todo();
}
});
$spinner.hide().appendTo($div);
$ok.hide().appendTo($div);
return $div;
};
// Pad Creation settings
var setHTML = function (e, html) {
e.innerHTML = html;
return e;
};
create['creation-owned'] = function () {
if (!common.isLoggedIn()) { return; }
var owned = h('div.cp-settings-creation-owned.cp-sidebarlayout-element', [
h('label', [
Messages.creation_ownedTitle
]),
setHTML(h('p.cp-sidebarlayout-description'),
Messages.creation_owned1 + '<br>' + Messages.creation_owned2),
h('input#cp-creation-owned-true.cp-creation-owned-value', {
type: 'radio',
name: 'cp-creation-owned',
value: 1,
checked: 'checked'
}),
h('label', { 'for': 'cp-creation-owned-true' }, Messages.creation_ownedTrue),
h('input#cp-creation-owned-false.cp-creation-owned-value', {
type: 'radio',
name: 'cp-creation-owned',
value: 0,
}),
h('label', { 'for': 'cp-creation-owned-false' }, Messages.creation_ownedFalse),
h('span.fa.fa-check', {title: Messages.saved}),
h('span.fa.fa-spinner.fa-pulse'),
]);
var $owned = $(owned);
var $ok = $owned.find('.fa-check').hide();
var $spinner = $owned.find('.fa-spinner').hide();
$owned.find('input').change(function () {
$spinner.show();
$ok.hide();
var val = parseInt($owned.find('[name="cp-creation-owned"]:checked').val());
common.setAttribute(['general', 'creation', 'owned'], val, function (e) {
if (e) { return void console.error(e); }
$spinner.hide();
$ok.show();
});
});
common.getAttribute(['general', 'creation', 'owned'], function (e, val) {
if (!val && typeof val !== "undefined") {
$owned.find('#cp-creation-owned-false').attr('checked', true);
}
});
return $owned;
};
create['creation-expire'] = function () {
if (!common.isLoggedIn()) { return; }
var expire = h('div.cp-settings-creation-expire.cp-sidebarlayout-element', [
h('label', [
Messages.creation_expireTitle
]),
setHTML(h('p.cp-sidebarlayout-description'),
Messages.creation_expire1 + '<br>' + Messages.creation_expire2),
h('input#cp-creation-expire-false.cp-creation-expire-value', {
type: 'radio',
name: 'cp-creation-expire',
value: 0,
checked: 'checked'
}),
h('label', { 'for': 'cp-creation-expire-false' }, Messages.creation_expireFalse),
h('input#cp-creation-expire-true.cp-creation-expire-value', {
type: 'radio',
name: 'cp-creation-expire',
value: 1
}),
h('label', { 'for': 'cp-creation-expire-true' }, [
Messages.creation_expireTrue,
h('span.cp-creation-expire-picker', [
h('input#cp-creation-expire-val', {
type: "number",
min: 1,
max: 100,
value: 3
}),
h('select#cp-creation-expire-unit', [
h('option', { value: 'hour' }, Messages.creation_expireHours),
h('option', { value: 'day' }, Messages.creation_expireDays),
h('option', {
value: 'month',
selected: 'selected'
}, Messages.creation_expireMonths)
])
])
]),
h('span.fa.fa-check', {title: Messages.saved}),
h('span.fa.fa-spinner.fa-pulse'),
]);
var $expire = $(expire);
var $ok = $expire.find('.fa-check').hide();
var $spinner = $expire.find('.fa-spinner').hide();
var getValue = function () {
if(!parseInt($expire.find('[name="cp-creation-expire"]:checked').val())) { return 0; }
var unit = 0;
switch ($expire.find('#cp-creation-expire-unit').val()) {
case "hour" : unit = 3600; break;
case "day" : unit = 3600 * 24; break;
case "month": unit = 3600 * 24 * 30; break;
default: unit = 0;
}
return ($expire.find('#cp-creation-expire-val').val() || 0) * unit;
};
$expire.find('input, select').change(function () {
$spinner.show();
$ok.hide();
common.setAttribute(['general', 'creation', 'expire'], getValue(), function (e) {
if (e) { return void console.error(e); }
$spinner.hide();
$ok.show();
});
});
common.getAttribute(['general', 'creation', 'expire'], function (e, val) {
UIElements.setExpirationValue(val, $expire);
});
return $expire;
};
create['creation-skip'] = function () {
if (!common.isLoggedIn()) { return; }
var skip = h('div.cp-settings-creation-skip.cp-sidebarlayout-element', [
h('label', [
Messages.settings_creationSkip
]),
setHTML(h('p.cp-sidebarlayout-description'), Messages.settings_creationSkipHint),
h('input#cp-creation-skip-true.cp-creation-skip-value', {
type: 'radio',
name: 'cp-creation-skip',
value: 1,
}),
h('label', { 'for': 'cp-creation-skip-true' }, Messages.settings_creationSkipTrue),
h('input#cp-creation-skip-false.cp-creation-skip-value', {
type: 'radio',
name: 'cp-creation-skip',
value: 0,
checked: 'checked'
}),
h('label', { 'for': 'cp-creation-skip-false' }, Messages.settings_creationSkipFalse),
h('span.fa.fa-check', {title: Messages.saved}),
h('span.fa.fa-spinner.fa-pulse'),
]);
var $div = $(skip);
var $ok = $div.find('.fa-check').hide();
var $spinner = $div.find('.fa-spinner').hide();
$div.find('input').change(function () {
$spinner.show();
$ok.hide();
var val = parseInt($div.find('[name="cp-creation-skip"]:checked').val());
// If we don't skip the pad creation screen, we dont' need settings to hide the templates
// modal
if (!val) {
$('.cp-settings-creation-template').addClass('cp-settings-creation-skipped');
} else {
$('.cp-settings-creation-template').removeClass('cp-settings-creation-skipped');
}
common.setAttribute(['general', 'creation', 'skip'], val, function (e) {
if (e) { return void console.error(e); }
$spinner.hide();
$ok.show();
});
});
common.getAttribute(['general', 'creation', 'skip'], function (e, val) {
if (val) {
$div.find('#cp-creation-skip-true').attr('checked', true);
return;
}
// If we don't skip the pad creation screen, we dont' need settings to hide the templates
// modal
$('.cp-settings-creation-template').addClass('cp-settings-creation-skipped');
});
return $div;
};
create['creation-template'] = function () {
var skip = h('div.cp-settings-creation-template.cp-sidebarlayout-element', [
h('label', [
Messages.settings_templateSkip
]),
setHTML(h('p.cp-sidebarlayout-description'), Messages.settings_templateSkipHint),
h('input#cp-creation-template-true.cp-creation-template-value', {
type: 'radio',
name: 'cp-creation-template',
value: 1,
}),
h('label', { 'for': 'cp-creation-template-true' }, Messages.settings_creationSkipTrue),
h('input#cp-creation-template-false.cp-creation-template-value', {
type: 'radio',
name: 'cp-creation-template',
value: 0,
checked: 'checked'
}),
h('label', { 'for': 'cp-creation-template-false' }, Messages.settings_creationSkipFalse),
h('span.fa.fa-check', {title: Messages.saved}),
h('span.fa.fa-spinner.fa-pulse'),
]);
var $div = $(skip);
var $ok = $div.find('.fa-check').hide();
var $spinner = $div.find('.fa-spinner').hide();
$div.find('input').change(function () {
$spinner.show();
$ok.hide();
var val = parseInt($div.find('[name="cp-creation-template"]:checked').val());
common.setAttribute(['general', 'creation', 'noTemplate'], val, function (e) {
if (e) { return void console.error(e); }
$spinner.hide();
$ok.show();
});
});
common.getAttribute(['general', 'creation', 'noTemplate'], function (e, val) {
if (val) {
$div.find('#cp-creation-template-true').attr('checked', true);
}
});
return $div;
};
// Drive settings
create['drive-duplicate'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', {
'class': 'cp-settings-drive-duplicate cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_driveDuplicateTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_driveDuplicateHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-drive-duplicate',
Messages.settings_driveDuplicateLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['drive', 'hideDuplicate'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['drive', 'hideDuplicate'], function (e, val) {
if (e) { return void console.error(e); }
if (val) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
create['resettips'] = function () {
var $div = $('<div>', {'class': 'cp-settings-resettips cp-sidebarlayout-element'});
$('<label>').text(Messages.settings_resetTips).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_resetTipsButton).appendTo($div);
var $button = $('<button>', {'id': 'cp-settings-resettips', 'class': 'btn btn-primary'})
.text(Messages.settings_resetTipsAction).appendTo($div);
var localStore = window.cryptpadStore;
$button.click(function () {
Object.keys(localStore).forEach(function (k) {
if(k.slice(0, 9) === "hide-info") {
localStore.put(k, undefined);
}
});
UI.alert(Messages.settings_resetTipsDone);
});
return $div;
};
create['thumbnails'] = function () {
var $div = $('<div>', {'class': 'cp-settings-thumbnails cp-sidebarlayout-element'});
$('<label>').text(Messages.settings_thumbnails).appendTo($div);
// Disable
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_disableThumbnailsDescription).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('disableThumbnails',
Messages.settings_disableThumbnailsAction,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked') || false;
common.setAttribute(['general', 'disableThumbnails'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['general', 'disableThumbnails'], function (e, val) {
$checkbox[0].checked = typeof(val) === "undefined" || val;
});
// Reset
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_resetThumbnailsDescription).appendTo($div);
var $button = $('<button>', {'id': 'resetThumbnails', 'class': 'btn btn-primary'})
.text(Messages.settings_resetThumbnailsAction).appendTo($div);
$button.click(function () {
sframeChan.query("Q_THUMBNAIL_CLEAR", null, function (err) {
if (err) { return void console.error("Cannot clear localForage"); }
UI.alert(Messages.settings_resetThumbnailsDone);
});
});
return $div;
};
var createExportUI = function () {
var progress = h('div.cp-export-progress');
var actions = h('div.cp-export-actions');
var errors = h('div.cp-export-errors', [
h('p', Messages.settings_exportErrorDescription)
]);
var exportUI = h('div#cp-export-container', [
h('div.cp-export-block', [
h('h3', Messages.settings_exportTitle),
h('p', [
Messages.settings_exportDescription,
h('br'),
Messages.settings_exportFailed,
h('br'),
h('strong', Messages.settings_exportWarning),
]),
progress,
actions,
errors
])
]);
$('body').append(exportUI);
$('#cp-sidebarlayout-container').hide();
var close = function () {
$(exportUI).remove();
$('#cp-sidebarlayout-container').show();
};
var _onCancel = [];
var onCancel = function (h) {
if (typeof (h) !== "function") { return; }
_onCancel.push(h);
};
var cancel = h('button.btn.btn-default', Messages.cancel);
$(cancel).click(function () {
UI.confirm(Messages.settings_exportCancel, function (yes) {
if (!yes) { return; }
_onCancel.forEach(function (h) { h(); });
});
}).appendTo(actions);
var error = h('button.btn.btn-danger', Messages.settings_exportError);
var translateErrors = function (err) {
if (err === 'EEMPTY') {
return Messages.settings_exportErrorEmpty;
}
if (['E404', 'EEXPIRED', 'EDELETED'].indexOf(err) !== -1) {
return Messages.settings_exportErrorMissing;
}
return Messages._getKey('settings_exportErrorOther', [err]);
};
var addErrors = function (errs) {
if (!errs.length) { return; }
var onClick = function () {
console.error('clicked?');
$(errors).toggle();
};
$(error).click(onClick).appendTo(actions);
var list = h('div.cp-export-errors-list');
$(list).appendTo(errors);
errs.forEach(function (err) {
if (!err.data) { return; }
var href = err.data.href || err.data.roHref;
$(h('div', [
h('div.title', err.data.filename || err.data.title),
h('div.link', [
h('a', {
href: err.data.href || err.data.roHref,
target: '_blank'
}, privateData.origin + href)
]),
h('div.reason', translateErrors(err.error))
])).appendTo(list);
});
};
var download = h('button.btn.btn-primary', Messages.download_mt_button);
var completed = false;
var complete = function (h, err) {
if (completed) { return; }
completed = true;
$(progress).find('.fa-square-o').removeClass('fa-square-o')
.addClass('fa-check-square-o');
$(cancel).text(Messages.filePicker_close).off('click').click(function () {
_onCancel.forEach(function (h) { h(); });
});
$(download).click(h).appendTo(actions);
addErrors(err);
};
var done = {};
var update = function (step, state) {
console.log(step, state);
console.log(done[step]);
if (done[step] && done[step] === -1) { return; }
// New step
if (!done[step]) {
$(progress).find('.fa-square-o').removeClass('fa-square-o')
.addClass('fa-check-square-o');
$(progress).append(h('p', [
h('span.fa.fa-square-o'),
h('span.text', Messages['settings_export_'+step] || step)
]));
done[step] = state; // -1 if no bar, object otherwise
if (state !== -1) {
var bar = h('div.cp-export-progress-bar');
$(progress).append(h('div.cp-export-progress-bar-container', [
bar
]));
done[step] = { bar: bar };
}
return;
}
// Updating existing step
if (typeof state !== "object") { return; }
var b = done[step].bar;
var w = (state.current/state.max) * 100;
$(b).css('width', w + '%');
if (!done[step].text) {
done[step].text = h('div.cp-export-progress-text');
$(done[step].text).appendTo(b);
}
$(done[step].text).text(state.current + ' / ' + state.max);
if (state.current === state.max) { done[step] = -1; }
};
return {
close: close,
update: update,
complete: complete,
onCancel: onCancel
};
};
create['drive-backup'] = function () {
var $div = $('<div>', {'class': 'cp-settings-drive-backup cp-sidebarlayout-element'});
var accountName = privateData.accountName;
var displayName = metadataMgr.getUserData().name || '';
var name = displayName || accountName || Messages.anonymous;
var suggestion = name + '-' + new Date().toDateString();
var exportFile = function () {
sframeChan.query("Q_SETTINGS_DRIVE_GET", null, function (err, data) {
if (err) { return void console.error(err); }
var sjson = JSON.stringify(data);
UI.prompt(Messages.exportPrompt,
Util.fixFileName(suggestion) + '.json', function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; }
var blob = new Blob([sjson], {type: "application/json;charset=utf-8"});
saveAs(blob, filename);
});
});
};
var importFile = function (content) {
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).appendTo($div);
try {
var data = JSON.parse(content);
sframeChan.query("Q_SETTINGS_DRIVE_SET", data, function (e) {
if (e) { console.error(e); }
$spinner.remove();
});
} catch (e) {
console.error(e);
}
};
$('<label>', {'for' : 'exportDrive'}).text(Messages.settings_backupCategory).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_backupHint || Messages.settings_backupTitle).appendTo($div);
/* add an export button */
var $export = common.createButton('export', true, {}, exportFile);
$export.attr('class', 'btn btn-success').text(Messages.settings_backup);
$div.append($export);
/* add an import button */
var $import = common.createButton('import', true, {}, importFile);
$import.attr('class', 'btn btn-success').text(Messages.settings_restore);
$div.append($import);
// Backup all the pads
var exportDrive = function () {
var todo = function (data, filename) {
var getPad = function (data, cb) {
sframeChan.query("Q_CRYPTGET", data, function (err, obj) {
if (err) { return void cb(err); }
if (obj.error) { return void cb(obj.error); }
cb(null, obj.data);
}, { timeout: 60000 });
};
var ui = createExportUI();
var bu = Backup.create(data, getPad, function (blob, errors) {
console.log(blob);
saveAs(blob, filename);
sframeChan.event('EV_CRYPTGET_DISCONNECT');
ui.complete(function () {
saveAs(blob, filename);
}, errors);
}, ui.update);
ui.onCancel(function () {
ui.close();
bu.stop();
});
};
sframeChan.query("Q_SETTINGS_DRIVE_GET", "full", function (err, data) {
if (err) { return void console.error(err); }
if (data.error) { return void console.error(data.error); }
UI.prompt(Messages.settings_backup2Confirm,
Util.fixFileName(suggestion) + '.zip', function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; }
todo(data, filename);
});
});
};
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_backupHint2).appendTo($div);
var $export2 = common.createButton('export', true, {}, exportDrive);
$export2.attr('class', 'btn btn-success').text(Messages.settings_backup2);
$div.append($export2);
return $div;
};
create['drive-import-local'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', {'class': 'cp-settings-drive-import-local cp-sidebarlayout-element'});
$('<label>', {'for' : 'cp-settings-import-local-pads'})
.text(Messages.settings_import).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_importTitle).appendTo($div);
var $button = $('<button>', {
'id': 'cp-settings-import-local-pads',
'class': 'btn btn-primary'
}).text(Messages.settings_import).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide().appendTo($div);
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide().appendTo($div);
$button.click(function () {
UI.confirm(Messages.settings_importConfirm, function (yes) {
if (!yes) { return; }
$spinner.show();
$ok.hide();
sframeChan.query('Q_SETTINGS_IMPORT_LOCAL', null, function () {
$spinner.hide();
$ok.show();
UI.alert(Messages.settings_importDone);
});
}, undefined, true);
});
return $div;
};
create['drive-reset'] = function () {
var $div = $('<div>', {'class': 'cp-settings-drive-reset cp-sidebarlayout-element'});
$('<label>').text(Messages.settings_resetNewTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_reset).appendTo($div);
var $button = $('<button>', {'id': 'cp-settings-reset-drive', 'class': 'btn btn-danger'})
.text(Messages.settings_resetButton).appendTo($div);
$button.click(function () {
UI.prompt(Messages.settings_resetPrompt, "", function (val) {
if (val !== "I love CryptPad") {
UI.alert(Messages.settings_resetError);
return;
}
sframeChan.query("Q_SETTINGS_DRIVE_RESET", null, function (err) {
if (err) { return void console.error(err); }
UI.alert(Messages.settings_resetDone);
});
}, undefined, true);
});
return $div;
};
// Cursor settings
create['cursor-color'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-cursor-color cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_cursorColorTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_cursorColorHint).appendTo($div);
var $inputBlock = $('<div>').appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $input = $('<input>', {
type: 'color',
}).on('change', function () {
var val = $input.val();
if (!/^#[0-9a-fA-F]{6}$/.test(val)) { return; }
$spinner.show();
$ok.hide();
common.setAttribute(['general', 'cursor', 'color'], val, function () {
$spinner.hide();
$ok.show();
});
}).appendTo($inputBlock);
$ok.hide().appendTo($inputBlock);
$spinner.hide().appendTo($inputBlock);
common.getAttribute(['general', 'cursor', 'color'], function (e, val) {
if (e) { return void console.error(e); }
$input.val(val || '');
});
return $div;
};
create['cursor-share'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-cursor-share cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_cursorShareTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_cursorShareHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-cursor-share',
Messages.settings_cursorShareLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['general', 'cursor', 'share'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['general', 'cursor', 'share'], function (e, val) {
if (e) { return void console.error(e); }
if (val !== false) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
create['cursor-show'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-cursor-show cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_cursorShowTitle + ' (BETA)').appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_cursorShowHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-cursor-show',
Messages.settings_cursorShowLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['general', 'cursor', 'show'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['general', 'cursor', 'show'], function (e, val) {
if (e) { return void console.error(e); }
if (val !== false) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
// Rich text pads settings
create['pad-width'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-pad-width cp-sidebarlayout-element'
});
$('<span>', {'class': 'label'}).text(Messages.settings_padWidth).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_padWidthHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-padwidth',
Messages.settings_padWidthLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['pad', 'width'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['pad', 'width'], function (e, val) {
if (e) { return void console.error(e); }
if (val) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
create['pad-spellcheck'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-pad-spellcheck cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_padSpellcheckTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_padSpellcheckHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-pad-spellcheck',
Messages.settings_padSpellcheckLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['pad', 'spellcheck'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['pad', 'spellcheck'], function (e, val) {
if (e) { return void console.error(e); }
if (val) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
// Code settings
create['code-indent-unit'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-code-indent-unit cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_codeIndentation).appendTo($div);
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input-block',
}).appendTo($div);
var $input = $('<input>', {
'min': 1,
'max': 8,
type: 'number',
}).on('change', function () {
var val = parseInt($input.val());
if (typeof(val) !== 'number') { return; }
common.setAttribute(['codemirror', 'indentUnit'], val);
}).appendTo($inputBlock);
common.getAttribute(['codemirror', 'indentUnit'], function (e, val) {
if (e) { return void console.error(e); }
if (typeof(val) !== 'number') {
$input.val(2);
} else {
$input.val(val);
}
});
return $div;
};
create['code-indent-type'] = function () {
var key = 'indentWithTabs';
var $div = $('<div>', {
'class': 'cp-settings-code-indent-type cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_codeUseTabs).appendTo($div);
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input-block',
}).css('flex-flow', 'column')
.appendTo($div);
var $cbox = $(UI.createCheckbox('cp-settings-codeindent'));
var $checkbox = $cbox.find('input').on('change', function () {
var val = $checkbox.is(':checked');
if (typeof(val) !== 'boolean') { return; }
common.setAttribute(['codemirror', key], val);
});
$cbox.appendTo($inputBlock);
/*proxy.on('change', ['settings', 'codemirror', key], function (o, n) {
$input[0].checked = !!n;
});*/
common.getAttribute(['codemirror', key], function (e, val) {
if (e) { return void console.error(e); }
$checkbox[0].checked = !!val;
});
return $div;
};
create['code-font-size'] = function () {
var key = 'fontSize';
var $div = $('<div>', {
'class': 'cp-settings-code-font-size cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_codeFontSize).appendTo($div);
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input-block',
}).appendTo($div);
var $input = $('<input>', {
'min': 8,
'max': 30,
type: 'number',
}).on('change', function () {
var val = parseInt($input.val());
if (typeof(val) !== 'number') { return; }
common.setAttribute(['codemirror', key], val);
}).appendTo($inputBlock);
common.getAttribute(['codemirror', key], function (e, val) {
if (e) { return void console.error(e); }
if (typeof(val) !== 'number') {
$input.val(12);
} else {
$input.val(val);
}
});
return $div;
};
// Settings app
var createUsageButton = function () {
common.createUsageBar(function (err, $bar) {
if (err) { return void console.error(err); }
APP.$usage.html('').append($bar);
}, true);
};
var hideCategories = function () {
APP.$rightside.find('> div').hide();
};
var showCategories = function (cat) {
hideCategories();
cat.forEach(function (c) {
APP.$rightside.find('.'+c).show();
});
};
var createLeftside = function () {
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
.appendTo(APP.$leftside);
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
var active = privateData.category || 'account';
Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
if (key === 'account') { $category.append($('<span>', {'class': 'fa fa-user-o'})); }
if (key === 'drive') { $category.append($('<span>', {'class': 'fa fa-hdd-o'})); }
if (key === 'cursor') { $category.append($('<span>', {'class': 'fa fa-i-cursor' })); }
if (key === 'code') { $category.append($('<span>', {'class': 'fa fa-file-code-o' })); }
if (key === 'pad') { $category.append($('<span>', {'class': 'fa fa-file-word-o' })); }
if (key === 'creation') { $category.append($('<span>', {'class': 'fa fa-plus-circle' })); }
if (key === 'subscription') { $category.append($('<span>', {'class': 'fa fa-star-o' })); }
if (key === active) {
$category.addClass('cp-leftside-active');
}
$category.click(function () {
if (!Array.isArray(categories[key]) && categories[key].onClick) {
categories[key].onClick();
return;
}
active = key;
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
$category.addClass('cp-leftside-active');
showCategories(categories[key]);
});
$category.append(Messages['settings_cat_'+key]);
});
showCategories(categories[active]);
};
nThen(function (waitFor) {
$(waitFor(UI.addLoadingScreen));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
APP.$container = $('#cp-sidebarlayout-container');
APP.$toolbar = $('#cp-toolbar');
APP.$leftside = $('<div>', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
sframeChan = common.getSframeChannel();
sframeChan.onReady(waitFor());
}).nThen(function (/*waitFor*/) {
metadataMgr = common.getMetadataMgr();
privateData = metadataMgr.getPrivateData();
// Toolbar
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
var configTb = {
displayed: displayed,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.settings_title,
metadataMgr: common.getMetadataMgr(),
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
// Content
var $rightside = APP.$rightside;
/*for (var f in create) {
if (typeof create[f] !== "function") { continue; }
$rightside.append(create[f]());
}*/
var addItem = function (cssClass) {
var item = cssClass.slice(12); // remove 'cp-settings-'
if (typeof (create[item]) === "function") {
$rightside.append(create[item]());
}
};
for (var cat in categories) {
if (!Array.isArray(categories[cat])) { continue; }
categories[cat].forEach(addItem);
}
// TODO RPC
//obj.proxy.on('change', [], refresh);
//obj.proxy.on('remove', [], refresh);
//Cryptpad.onDisplayNameChanged(refresh);
createLeftside();
createUsageButton();
UI.removeLoadingScreen();
});
});