edgeconnect-cryptpad-deploy.../www/settings/inner.js
Richard Robert Reitz addc3725ea
Some checks failed
build / build (push) Has been cancelled
first commit
2025-08-28 15:13:14 +02:00

2272 lines
84 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'jquery',
'/common/toolbar.js',
'/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',
'/common/inner/sidebar-layout.js',
'/common/inner/badges.js',
'/common/inner/common-mediatag.js',
'/customize/messages.js',
'/common/hyperscript.js',
'/common/common-credential.js',
'/customize/application_config.js',
'/api/config',
'/common/make-backup.js',
'/common/common-feedback.js',
'/common/common-constants.js',
'/customize.dist/login.js',
'/common/sframe-common-codemirror.js',
'cm/lib/codemirror',
'cm/mode/gfm/gfm',
'/common/jscolor.js',
'/components/file-saver/FileSaver.min.js',
'css!/components/codemirror/lib/codemirror.css',
'css!/components/codemirror/addon/dialog/dialog.css',
'css!/components/codemirror/addon/fold/foldgutter.css',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
'less!/settings/app-settings.less',
], function(
$,
Toolbar,
nThen,
SFCommon,
UI,
UIElements,
Util,
Hash,
Sidebar,
Badges,
MT,
Messages,
h,
Cred,
AppConfig,
ApiConfig,
Backup,
Feedback,
Constants,
Login,
SFCodeMirror,
CodeMirror
) {
var saveAs = window.saveAs;
var APP = window.APP = {};
var common;
var metadataMgr;
var privateData;
var sframeChan;
var onProfileEvt = Util.mkEvent();
var categories = {
'account': [ // Msg.settings_cat_account
'cp-settings-own-drive',
'cp-settings-info-block',
'cp-settings-language-selector',
'cp-settings-mediatag-size',
'cp-settings-delete'
],
'profile': [ // Msg.settings_cat_profile
'cp-settings-profile-header',
'cp-settings-profile-name',
'cp-settings-profile-avatar',
'cp-settings-profile-badges',
'cp-settings-profile-link',
'cp-settings-profile-description',
],
'security': [ // Msg.settings_cat_security
'cp-settings-logout-everywhere',
'cp-settings-mfa',
'cp-settings-change-password',
'cp-settings-safe-links',
'cp-settings-userfeedback',
'cp-settings-cache',
'cp-settings-remove-owned'
],
'style': [ // Msg.settings_cat_style
'cp-settings-colortheme',
'cp-settings-custom-theme',
],
'drive': [
'cp-settings-redirect',
'cp-settings-resettips',
'cp-settings-autostore',
'cp-settings-drive-duplicate',
'cp-settings-thumbnails',
'cp-settings-drive-backup',
'cp-settings-drive-import-local',
'cp-settings-trim-history',
//'cp-settings-drive-reset'
],
'cursor': [ // Msg.settings_cat_cursor
'cp-settings-cursor-color',
'cp-settings-cursor-share',
'cp-settings-cursor-show',
],
'pad': [ // Msg.settings_cat_pad
'cp-settings-pad-width',
'cp-settings-pad-spellcheck',
'cp-settings-pad-notif',
'cp-settings-pad-openlink',
],
'code': [ // Msg.settings_cat_code
'cp-settings-code-indent-unit',
'cp-settings-code-indent-type',
'cp-settings-code-brackets',
'cp-settings-code-font-size',
'cp-settings-code-spellcheck',
],
'kanban': [ // Msg.settings_cat_kanban
'cp-settings-kanban-tags',
],
'notifications': [
'cp-settings-notif-calendar'
]
};
if (AppConfig.disableFeedback) {
var feedbackIdx = categories.security.indexOf('cp-settings-userfeedback');
categories.security.splice(feedbackIdx, 1);
}
if (AppConfig.disableProfile) {
delete categories.profile;
}
var create = {};
var SPECIAL_HINTS_HANDLER = {
safeLinks: function() {
return $('<span>', { 'class': 'cp-sidebarlayout-description' })
.html(Messages._getKey('settings_safeLinksHint', ['<span class="fa fa-shhare-alt"></span>']));
},
};
var DEFAULT_HINT_HANDLER = function(safeKey) {
return $('<span>', { 'class': 'cp-sidebarlayout-description' })
.text(Messages['settings_' + safeKey + 'Hint'] || 'Coming soon...');
};
var makeBlock = function(key, getter, full, isNew) {
var safeKey = key.replace(/-([a-z])/g, function(g) { return g[1].toUpperCase(); });
create[key] = function() {
var $div = $('<div>', { 'class': 'cp-settings-' + key + ' cp-sidebarlayout-element' });
if (isNew) {
$div.attr('data-item', key);
}
if (full) {
$('<label>').text(Messages['settings_' + safeKey + 'Title'] || key).appendTo($div);
// if this block's hint needs a special renderer, then create it in SPECIAL_HINTS_HANLDER
// otherwise the default will be used
var hintFunction = (typeof(SPECIAL_HINTS_HANDLER[safeKey]) === 'function') ?
SPECIAL_HINTS_HANDLER[safeKey] :
DEFAULT_HINT_HANDLER;
hintFunction(safeKey).appendTo($div);
}
getter(function(content) {
if (content === false) {
$div.remove();
$div = undefined;
return;
}
$div.append(content);
}, $div);
return $div;
};
};
// 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': 'cp-default-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.getPublicSigningKeyString(privateData.origin, accountName, publicKey);
var $pubLabel = $('<label>', { 'class': 'cp-default-label', 'for': 'publicKey' })
.text(Messages.settings_publicSigningKey);
var $pubInput = $('<input>', { 'type': 'text', 'value': userHref, 'id': 'publicKey' });
$key.append($pubLabel).append($pubInput);
}
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': 'cp-default-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-radio-container', [
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': 'cp-default-label' }).text(Messages.settings_userFeedbackTitle).appendTo($div);
$div.append(h('span.cp-sidebarlayout-description', [
Messages.settings_userFeedbackHint1,
Messages.settings_userFeedbackHint2,
]));
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;
};
makeBlock('cache', function (cb) { // Msg.settings_cacheHint, .settings_cacheTitle
var store = window.cryptpadStore;
var $cbox = $(UI.createCheckbox('cp-settings-cache-1',
Messages.settings_cacheCheckbox,
false, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
// Checkbox: "Enable safe links"
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = !$checkbox.is(':checked') ? '1' : undefined;
store.put('disableCache', val, function () {
sframeChan.query('Q_CACHE_DISABLE', {
disabled: Boolean(val)
}, function () {
spinner.done();
});
});
});
store.get('disableCache', function (val) {
if (!val) {
$checkbox.attr('checked', 'checked');
}
});
var button = h('button.btn.btn-danger', [
h('i.fa.fa-trash-o'),
h('span', Messages.settings_cacheButton)
]);
var buttonContainer = h('div.cp-settings-clear-cache', button);
var spinner2 = UI.makeSpinner($(buttonContainer));
UI.confirmButton(button, {
classes: 'btn-danger'
}, function () {
spinner2.spin();
sframeChan.query('Q_CLEAR_CACHE', null, function() {
spinner2.done();
});
});
cb([
$cbox[0],
buttonContainer
]);
}, true);
makeBlock('colortheme', function (cb) { // Msg.settings_colorthemeHint .settings_colorthemeTitle
var theme = window.cryptpadStore.store['colortheme'] || 'default';
var os = window.cryptpadStore.store['colortheme_default'] || 'light';
var values = [
'default', // Msg.settings_colortheme_default
'light', // Msg.settings_colortheme_light
'dark', // Msg.settings_colortheme_dark
/* 'custom'*/ // Msg.settings_colortheme_custom
];
var defaultTheme = Messages['settings_colortheme_'+os];
var opts = h('div.cp-settings-radio-container', [
values.map(function (key) {
return UI.createRadio('cp-colortheme-radio', 'cp-colortheme-radio-'+key,
Messages._getKey('settings_colortheme_' + key, [defaultTheme]),
key === theme, {
input: { value: key },
label: { class: 'noTitle' }
});
})
]);
cb(opts);
var spinner = UI.makeSpinner($(opts));
$(opts).find('input[name="cp-colortheme-radio"]').change(function () {
var val = this.value;
if (values.indexOf(val) === -1) { return; }
if (val === theme) { return; }
spinner.spin();
// Check if we need to flush cache
var flush = false;
if (val === "default" && os === theme) {
// Switch from a theme to default without changing value: nothing to do
} else if (theme === "default" && os === val) {
// Switch from default to a selected value without any change: nothing to do
} else {
// The theme is different, flush cache
flush = true;
}
if (val === 'default') { val = ''; }
// browsers try to load iframes from cache if they have the same id as was previously seen
// this seems to help?
window.location.hash = '';
if (flush && window.CryptPad_flushCacheInner) { window.CryptPad_flushCacheInner(); }
sframeChan.query('Q_COLORTHEME_CHANGE', {
theme: val,
flush: flush
}, function () {
window.cryptpadStore.store['colortheme'] = val;
theme = val || 'default';
spinner.done();
});
});
}, true);
var deriveBytes = function (name, password, cb) {
Cred.deriveFromPassphrase(name, password, Login.requiredBytes, cb);
};
makeBlock('remove-owned', function(cb) { // Msg.settings_removeOwnedHint, .settings_removeOwnedTitle
if (!common.isLoggedIn()) { return cb(false); }
var button = h('button.btn.btn-danger', [
h('i.cptools.cptools-destroy'),
Messages.settings_removeOwnedButton
]);
var form = h('div', [
button
]);
var $button = $(button);
UI.confirmButton(button, {
classes: 'btn-danger',
multiple: true
}, function() {
UI.addLoadingScreen({
hideTips: true,
loadingText: Messages.settings_removeOwnedText
});
sframeChan.query("Q_SETTINGS_REMOVE_OWNED_PADS", {}, function (err, data) {
UI.removeLoadingScreen();
$button.prop('disabled', '');
if (data && data.error) {
console.error(data.error);
return void UI.warn(Messages.error);
}
UI.log(Messages.ui_success);
});
});
cb(form);
}, true);
makeBlock('delete', function(cb) { // Msg.settings_deleteHint, .settings_deleteTitle
if (!common.isLoggedIn()) { return cb(false); }
var button = h('button.btn.btn-danger', Messages.settings_deleteButton);
var form = h('div', [
UI.passwordInput({
id: 'cp-settings-delete-account',
placeholder: Messages.settings_changePasswordCurrent,
autocomplete: 'current-password',
}, true),
button
]);
var $form = $(form);
var $button = $(button);
UI.confirmButton(button, {
classes: 'btn-danger',
multiple: true
}, function() {
nThen(function (waitFor) {
$button.prop('disabled', 'disabled');
// Accounts plugin:
// Msg.settings_deleteSubscription
// Msg.settings_deleteWarning
// Msg.settings_deleteContinue
common.getExtensionsSync('ACCOUNT_DELETION').forEach(ext => {
if (!ext?.checkDeletion) {
return console.error('Invalid extension point', 'ACCOUNT_DELETION', ext);
}
ext.checkDeletion(common, $button, waitFor(allowed => {
if (allowed) { return; }
waitFor.abort();
}));
});
}).nThen(function () {
var password = $form.find('#cp-settings-delete-account').val();
if (!password) {
return void UI.warn(Messages.error);
}
UI.addLoadingScreen({
hideTips: true,
loadingText: Messages.settings_deleteTitle
});
setTimeout(function () {
var bytes;
var auth = {};
var ssoSeed;
nThen(function (w) {
sframeChan.query("Q_SETTINGS_GET_SSO_SEED", {
}, w(function (err, obj) {
if (!obj || !obj.seed) { return; } // Not an sso account?
ssoSeed = obj.seed;
}));
}).nThen(function (w) {
var name = ssoSeed || privateData.accountName;
deriveBytes(name, password, w(function (_bytes) {
bytes = _bytes;
}));
}).nThen(function (w) {
var result = Login.allocateBytes(bytes);
sframeChan.query("Q_SETTINGS_CHECK_PASSWORD", {
blockHash: result.blockHash,
userHash: result.userHash,
}, w(function (err, obj) {
if (!obj || !obj.correct) {
UI.warn(Messages.login_noSuchUser);
w.abort();
UI.removeLoadingScreen();
}
}));
}).nThen(function (w) {
// CHECK MFA
sframeChan.query('Q_SETTINGS_MFA_CHECK', {}, w(function (err, obj) {
// No block? no need for a code
if (err || !obj || (obj && obj.err === 'NOBLOCK')
|| !obj.mfa) { return; }
auth.type = obj.type;
if (auth.type === 'TOTP') {
UI.getOTPScreen(w(function (val) {
UI.addLoadingScreen({ loadingText: Messages.settings_deleteTitle });
auth.data = val;
}), function () {
w.abort(); // On exit OTP screen
});
}
}));
}).nThen(function () {
window.CP_ownAccountDeletion = true;
sframeChan.query("Q_SETTINGS_DELETE_ACCOUNT", {
bytes: bytes,
auth: auth
}, function(err, data) {
if (err) { window.CP_ownAccountDeletion = false; }
UI.removeLoadingScreen();
if (data && data.error) {
$button.prop('disabled', '');
if (data.error === 'INVALID_PASSWORD') {
return void UI.warn(Messages.drive_sfPasswordError);
}
if (data.error === 'INVALID_CODE') {
return void UI.warn(Messages.settings_otp_invalid);
}
return void UI.warn(Messages.error);
}
// Owned drive
if (data.state === true) {
return void sframeChan.query('Q_SETTINGS_LOGOUT_PROPERLY', null, function() {
UI.alert(Messages.settings_deleted, function() {
common.gotoURL('/');
});
});
}
// 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);
$button.prop('disabled', '');
});
});
});
});
});
cb(form);
}, true);
create['change-password'] = function() {
if (!common.isLoggedIn()) { return; }
if (privateData.isSSO && ApiConfig.sso && ApiConfig.sso.password === 0) { return; }
var $div = $('<div>', { 'class': 'cp-settings-change-password cp-sidebarlayout-element' });
$('<span>', { 'class': 'cp-default-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,
autocomplete: 'current-password',
}, true),
UI.passwordInput({
id: 'cp-settings-change-password-new',
placeholder: Messages.settings_changePasswordNew,
autocomplete: 'new-password',
}, true),
UI.passwordInput({
id: 'cp-settings-change-password-new2',
placeholder: Messages.settings_changePasswordNewConfirm,
autocomplete: 'new-password',
}, 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);
}, {raw: true});
};
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({ loadingText: Messages.settings_changePasswordPending });
// We're going to derive the bytes in inner in order to ask for the possible
// OTP code after the Scrypt execution. This will make it less likely to
// have the OTP code expire.
setTimeout(function () {
var oldBytes, newBytes;
var auth = {};
var ssoSeed;
nThen(function (w) {
sframeChan.query("Q_SETTINGS_GET_SSO_SEED", {
}, w(function (err, obj) {
if (!obj || !obj.seed) { return; } // Not an sso account?
ssoSeed = obj.seed;
}));
}).nThen(function (w) {
var name = ssoSeed || privateData.accountName;
deriveBytes(name, oldPassword, w(function (bytes) {
oldBytes = bytes;
}));
deriveBytes(name, newPassword, w(function (bytes) {
newBytes = bytes;
}));
}).nThen(function (w) {
var result = Login.allocateBytes(oldBytes);
sframeChan.query("Q_SETTINGS_CHECK_PASSWORD", {
blockHash: result.blockHash,
userHash: result.userHash,
}, w(function (err, obj) {
if (!obj || !obj.correct) {
UI.warn(Messages.login_noSuchUser);
w.abort();
UI.removeLoadingScreen();
}
}));
}).nThen(function (w) {
// CHECK MFA
sframeChan.query('Q_SETTINGS_MFA_CHECK', {}, w(function (err, obj) {
// No block? no need for a code
if (err || !obj || (obj && obj.err === 'NOBLOCK')
|| !obj.mfa) { return; }
auth.type = obj.type;
if (auth.type === 'TOTP') {
UI.getOTPScreen(w(function (val) {
auth.data = val;
UI.addLoadingScreen({ loadingText: Messages.settings_changePasswordPending });
}), function () {
w.abort(); // On exit OTP screen
});
}
}));
}).nThen(function () {
updateBlock({
password: oldPassword,
newPassword: newPassword,
oldBytes: oldBytes,
newBytes: newBytes,
auth: auth
}, function(obj) {
UI.removeLoadingScreen();
if (obj && obj.error) {
if (obj.error === 'INVALID_CODE') {
return void UI.warn(Messages.settings_otp_invalid);
}
// TODO more specific error message?
console.error(obj.error);
UI.alert(Messages.settings_changePasswordError);
}
});
});
});
}, {
ok: Messages.register_writtenPassword,
cancel: Messages.register_cancel,
okClass: 'btn.btn-danger.btn-confirm',
reverseOrder: true,
done: function($dialog) {
$dialog.find('> div').addClass('half');
},
});
};
$(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;
};
makeBlock('own-drive', function(cb, $div) { // Msg.settings_ownDriveHint, .settings_ownDriveTitle
if (privateData.isDriveOwned || !common.isLoggedIn()) {
return void cb(false);
}
$div.addClass('alert alert-warning');
var form = h('div', [
UI.passwordInput({
id: 'cp-settings-migrate-password',
placeholder: Messages.settings_changePasswordCurrent
}, true),
h('button.btn.btn-primary', Messages.settings_ownDriveButton)
]);
var $form = $(form);
var spinner = UI.makeSpinner($form);
var todo = function() {
var password = $form.find('#cp-settings-migrate-password').val();
if (!password) { return; }
spinner.spin();
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); }
spinner.done();
});
});
};
$form.find('button').click(function() {
todo();
});
$form.find('input').keydown(function(e) {
// Save on Enter
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
todo();
}
});
cb(form);
}, true);
makeBlock('mediatag-size', function(cb, $div) { // Msg.settings_mediatagSizeHint, .settings_mediatagSizeTitle
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input-block',
});
var spinner;
var $input = $(h('input#cp-automatic-download', {
'min': -1,
'max': 1000,
type: 'number',
})).appendTo($inputBlock);
$div.find('label').attr('for', 'cp-automatic-download');
var oldVal;
var todo = function () {
var val = parseInt($input.val());
if (typeof(val) !== 'number' || isNaN(val)) { return UI.warn(Messages.error_limit); }
if (val === oldVal) { return; }
spinner.spin();
common.setAttribute(['general', 'mediatag-size'], val, function (err) {
if (err) {
spinner.hide();
console.error(err);
return UI.warn(Messages.error);
}
oldVal = val;
spinner.done();
UI.log(Messages.saved);
});
};
var $save = $(h('button.btn.btn-primary', Messages.settings_save)).appendTo($inputBlock);
spinner = UI.makeSpinner($inputBlock);
$save.click(todo);
$input.on('keyup', function(e) {
if (e.which === 13) { todo(); }
});
common.getAttribute(['general', 'mediatag-size'], function(e, val) {
if (e) { return void console.error(e); }
if (typeof(val) !== 'number' || isNaN(val)) {
oldVal = 5;
$input.val(5);
} else {
oldVal = val;
$input.val(val);
}
});
cb($inputBlock);
}, true);
// Account access
makeBlock('mfa', function (cb) { // Msg.settings_mfaTitle, Msg.settings_mfaHint
if (!common.isLoggedIn()) { return void cb(false); }
var content = h('div');
sframeChan.query('Q_SETTINGS_MFA_CHECK', {}, function (err, obj) {
if (err || !obj || (obj && obj.err === 'NOBLOCK')) { return void cb(false); }
var enabled = obj && obj.mfa && obj.type === 'TOTP';
var config = {
accountName: privateData.accountName,
origin: privateData.origin
};
var draw = (state) => {
common.totpSetup(config, content, state, (newState) => {
draw(newState);
});
};
draw(Boolean(enabled));
cb(content);
});
}, true);
// Security
makeBlock('safe-links', function(cb) { // Msg.settings_safeLinksTitle
var $cbox = $(UI.createCheckbox('cp-settings-safe-links',
Messages.settings_safeLinksCheckbox,
false, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
// Checkbox: "Enable safe links"
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = !$checkbox.is(':checked');
common.setAttribute(['security', 'unsafeLinks'], val, function() {
spinner.done();
});
});
common.getAttribute(['security', 'unsafeLinks'], function(e, val) {
if (e) { return void console.error(e); }
if (val === false) {
$checkbox.attr('checked', 'checked');
}
});
cb($cbox);
}, true);
// 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['redirect'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-redirect cp-sidebarlayout-element' });
$('<span>', { 'class': 'cp-default-label' }).text(Messages.settings_driveRedirectTitle).appendTo($div);
$div.append(h('span', {
class: 'cp-sidebarlayout-description',
}, Messages.settings_driveRedirectHint));
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-redirect',
Messages.settings_driveRedirect,
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', Constants.prefersDriveRedirectKey], val, function() {
$spinner.hide();
$ok.show();
sframeChan.query("Q_SET_DRIVE_REDIRECT_PREFERENCE", {
value: val,
}, console.log);
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
if (privateData.prefersDriveRedirect === true) {
$checkbox[0].checked = true;
}
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.store).forEach(function(k) {
if (/^(hide-(info|alert))/.test(k)) {
localStore.put(k, null);
}
});
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;
};
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() {
Feedback.send('FULL_DRIVE_EXPORT_START');
var todo = function(data, filename) {
var ui = Backup.createExportUI(privateData.origin);
data.common = common;
var bu = Backup.create(data, common.getPad, privateData.fileHost, function(blob, errors) {
saveAs(blob, filename);
sframeChan.event('EV_CRYPTGET_DISCONNECT');
ui.complete(function() {
Feedback.send('FULL_DRIVE_EXPORT_COMPLETE');
saveAs(blob, filename);
}, errors);
}, ui.update, common.getCache(), common.getSframeChannel());
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>').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;
};
var redrawTrimHistory = function(cb, $div) {
var spinner = UI.makeSpinner();
var button = h('button.btn.btn-danger-alt', {
disabled: 'disabled'
}, Messages.trimHistory_button);
var currentSize = h('p', $(spinner.spinner).clone()[0]);
var content = h('div#cp-settings-trim-container', [
currentSize,
button,
spinner.ok,
spinner.spinner
]);
if (!privateData.isDriveOwned) {
var href = privateData.origin + privateData.pathname + '#' + 'account';
$(currentSize).html(Messages.trimHistory_needMigration);
$(currentSize).find('a').prop('href', href).click(function(e) {
e.preventDefault();
$('.cp-sidebarlayout-category[data-category="account"]').click();
});
return void cb(content);
}
var $button = $(button);
var size;
var channels = [];
nThen(function(waitFor) {
APP.history.execCommand('GET_HISTORY_SIZE', {
account: true,
channels: []
}, waitFor(function(obj) {
if (obj && obj.error) {
waitFor.abort();
var error = h('div.alert.alert-danger', Messages.trimHistory_getSizeError);
$(content).empty().append(error);
return;
}
channels = obj.channels;
size = Number(obj.size);
}));
}).nThen(function() {
if (!size || size < 1024) {
$(currentSize).html(Messages.trimHistory_noHistory);
return;
}
$(currentSize).html(Messages._getKey('trimHistory_currentSize', [UIElements.prettySize(size)]));
$button.prop('disabled', '');
UI.confirmButton(button, {
classes: 'btn-danger'
}, function() {
$button.remove();
spinner.spin();
APP.history.execCommand('TRIM_HISTORY', {
channels: channels
}, function(obj) {
if (obj && obj.error)  {
var error = h('div.alert.alert-danger', Messages.trimHistory_error);
$(content).empty().append(error);
return;
}
spinner.hide();
redrawTrimHistory(cb, $div);
});
});
});
$div.find('#cp-settings-trim-container').remove();
cb(content);
};
makeBlock('trim-history', function(cb, $div) { // Msg.settings_trimHistoryHint, .settings_trimHistoryTitle
if (!common.isLoggedIn()) { return void cb(false); }
redrawTrimHistory(cb, $div);
}, true);
/*
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 $colorPicker = $("<div>", { class: "cp-settings-cursor-color-picker" });
var $ok = $('<span>', { 'class': 'fa fa-check', title: Messages.saved });
var $spinner = $('<span>', { 'class': 'fa fa-spinner fa-pulse' });
// when jscolor picker value change
var _onchange = function(colorL) {
var val = "#" + colorL.toString();
if (!/^#[0-9a-fA-F]{6}$/.test(val)) { return; }
common.setAttribute(['general', 'cursor', 'color'], val, function() {
$spinner.hide();
$ok.show();
});
};
var to;
var onchange = function(colorL) {
$spinner.show();
$ok.hide();
if (to) { clearTimeout(to); }
to = setTimeout(function() {
_onchange(colorL);
}, 300);
};
// jscolor picker
var jscolorL = new window.jscolor($colorPicker[0], { showOnClick: false, onFineChange: onchange, valueElement: undefined });
$colorPicker.click(function() {
jscolorL.show();
});
// set default color
common.getAttribute(['general', 'cursor', 'color'], function(e, val) {
if (e) { return void console.error(e); }
val = val || "#000";
jscolorL.fromString(val);
});
$colorPicker.appendTo($inputBlock);
$ok.hide().appendTo($inputBlock);
$spinner.hide().appendTo($inputBlock);
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': 'cp-default-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 store = window.cryptpadStore;
var key = 'pad-small-width';
var isHidden = store.store[key] === '1';
var $cbox = $(UI.createCheckbox('cp-settings-padwidth',
Messages.settings_padWidthLabel,
isHidden, { label: { class: 'noTitle' } }));
var $checkbox = $cbox.find('input').on('change', function() {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
store.put(key, val ? '1' : '0', function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
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;
};
makeBlock('pad-notif', function(cb) { // Msg.settings_padNotifHint, .settings_padNotifTitle
var $cbox = $(UI.createCheckbox('cp-settings-pad-notif',
Messages.settings_padNotifCheckbox,
false, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
// Checkbox: "Enable safe links"
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = $checkbox.is(':checked');
common.setAttribute(['pad', 'disableNotif'], val, function() {
spinner.done();
});
});
common.getAttribute(['pad', 'disableNotif'], function(e, val) {
if (e) { return void console.error(e); }
if (val === true) {
$checkbox.attr('checked', 'checked');
}
});
cb($cbox);
}, true);
create['pad-openlink'] = function() {
var $div = $('<div>', {
'class': 'cp-settings-pad-openlink cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_padOpenLinkTitle).appendTo($div);
$('<span>', { 'class': 'cp-sidebarlayout-description' })
.text(Messages.settings_padOpenLinkHint).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-openlink',
Messages.settings_padOpenLinkLabel,
false, { label: { class: 'noTitle' } }));
var $checkbox = $cbox.find('input').on('change', function() {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['pad', 'openLink'], val, function() {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['pad', 'openLink'], 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)
.attr('for', 'indent-unit')
.appendTo($div);
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input',
}).appendTo($div);
var $input = $('<input>', {
'min': 1,
'max': 8,
type: 'number',
id: 'indent-unit',
}).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-brackets'] = function() {
var key = 'brackets';
var $div = $('<div>', {
'class': 'cp-settings-code-brackets cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_codeBrackets).appendTo($div);
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input-block',
}).css('flex-flow', 'column')
.appendTo($div);
var $cbox = $(UI.createCheckbox('cp-settings-codebrackets'));
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);
common.getAttribute(['codemirror', key], function(e, val) {
if (e) { return void console.error(e); }
$checkbox[0].checked = typeof(val) !== "boolean" || 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)
.attr('for', 'font-size')
.appendTo($div);
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input',
}).appendTo($div);
var $input = $('<input>', {
'min': 8,
'max': 30,
type: 'number',
id: 'font-size',
}).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;
};
create['code-spellcheck'] = function() {
var $div = $('<div>', {
'class': 'cp-settings-code-spellcheck cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_codeSpellcheckTitle).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-code-spellcheck',
Messages.settings_codeSpellcheckLabel,
false, { label: { class: 'noTitle' } }));
var $checkbox = $cbox.find('input').on('change', function() {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['codemirror', 'spellcheck'], val, function() {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['codemirror', 'spellcheck'], function(e, val) {
if (e) { return void console.error(e); }
if (val) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
makeBlock('kanban-tags', function(cb) { // Msg.settings_kanbanTagsHint, .settings_kanbanTagsTitle
var opt1 = UI.createRadio('cp-settings-kanban-tags', 'cp-settings-kanban-tags-and',
Messages.settings_kanbanTagsAnd, false, {
input: { value: 1 },
label: { class: 'noTitle' }
});
var opt2 = UI.createRadio('cp-settings-kanban-tags', 'cp-settings-kanban-tags-or',
Messages.settings_kanbanTagsOr, true, {
input: { value: 0 },
label: { class: 'noTitle' }
});
var div = h('div.cp-settings-radio-container', [
opt1,
opt2,
]);
var $d = $(div);
var spinner = UI.makeSpinner($d);
$d.find('input[type="radio"]').on('change', function() {
spinner.spin();
var val = $('input:radio[name="cp-settings-kanban-tags"]:checked').val();
val = Number(val) || 0;
common.setAttribute(['kanban', 'tagsAnd'], val, function() {
spinner.done();
});
});
common.getAttribute(['kanban', 'tagsAnd'], function(e, val) {
if (e) { return void console.error(e); }
if (val) {
$(opt1).find('input').attr('checked', 'checked');
}
});
cb($d);
}, true);
makeBlock('notif-calendar', function(cb) { // Msg.settings_notifCalendarHint, .settings_notifCalendarTitle
var $cbox = $(UI.createCheckbox('cp-settings-cache-2',
Messages.settings_notifCalendarCheckbox,
false, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = !$checkbox.is(':checked');
common.setAttribute(['general', 'calendar', 'hideNotif'], val, function(e) {
if (e) {
console.error(e);
// error: restore previous value
if (val) { $checkbox.attr('checked', ''); }
else { $checkbox.attr('checked', 'checked'); }
spinner.hide();
return void console.error(e);
}
spinner.done();
});
});
common.getAttribute(['general', 'calendar', 'hideNotif'], function(e, val) {
if (e) { return void console.error(e); }
if (!val) {
$checkbox.attr('checked', 'checked');
}
});
cb($cbox[0]);
}, true);
// Profile
makeBlock('profile-header', function(cb) {
cb();
}, true);
makeBlock('profile-name', function(cb) {
const input = APP.blocks.input();
const button = APP.blocks.button('primary', '',
Messages.settings_save);
const inputButton = APP.blocks.inputButton(input, button, {
onEnterDelegate: true
});
const labelled = APP.blocks.labelledInput(
Messages.user_displayName, input, inputButton);
let displayName = metadataMgr.getUserData().name || '';
const $input = $(input).val(displayName || '');
Util.onClickEnter($(button), () => {
const value = $(input).val();
if (value === displayName) { return; }
common.setDisplayName(value, function() {
displayName = value;
UI.log(Messages.saved);
});
});
onProfileEvt.reg(() => {
$input.val(APP.profileData?.name || '');
});
cb(labelled);
}, false, true);
makeBlock('profile-link', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const input = APP.blocks.input({
type: 'url'
});
const button = APP.blocks.button('primary', '',
Messages.settings_save);
const inputButton = APP.blocks.inputButton(input, button, {
onEnterDelegate: true
});
const labelled = APP.blocks.labelledInput(
Messages.profile_addLink, input, inputButton);
const $input = $(input).val(APP.profileData?.url || '');
Util.onClickEnter($(button), () => {
if ($input.is(':invalid')) { return; }
const value = $input.val();
APP.profile.execCommand('SET', {
key: 'url',
value
}, function (data) {
UI.log(Messages.saved);
APP.profileData = data;
onProfileEvt.fire();
});
});
onProfileEvt.reg(() => {
$input.val(APP.profileData?.url || '');
});
cb(labelled);
}, false, true);
const redrawBadges = ($badges) => {
const old = APP.profileData;
APP.badge.execCommand('LIST_BADGES', {}, data => {
let spinner;
$badges.toggle(!!data.length);
let all = data.map(str => {
const i = Badges.render(str);
const $i = $(i).attr('tabindex', 0);
const selected = old?.badge === str;
if (selected) { $i.addClass('cp-selected'); }
Util.onClickEnter($i, () => {
let value = selected ? '' : str;
spinner.spin();
APP.profile.execCommand('SET', {
key: 'badge',
value
}, function (data) {
APP.profileData = data;
spinner.done();
onProfileEvt.fire();
});
});
return i;
});
if (!all.length) {
return $badges.empty();
}
let content = h('div.cp-settings-badges', [
h('span', Messages.profile_badges),
h('div.cp-settings-badges-list', all)
]);
$badges.empty().append(content);
spinner = UI.makeSpinner($badges.find('.cp-settings-badges-list'));
});
};
makeBlock('profile-badges', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const badges = h('div');
const $badges = $(badges);
redrawBadges($badges);
onProfileEvt.reg(() => {
redrawBadges($badges);
});
cb(badges);
}, false, true);
const redrawAvatar = ($avatar) => {
const val = APP.profileData?.avatar;
const badge = APP.profileData?.badge;
const name = APP.profileData?.name || Messages.anonymous;
if (!val) { $avatar.empty(); }
common.displayAvatar($avatar, val, name, () => {
if (!val) { return; }
// avatar cb: append delete button
$avatar.find('.cp-settings-avatar-delete').remove();
const delButton = h('button.cp-settings-avatar-delete.btn.btn-danger.fa.fa-times', {
'aria-label': Messages.profile_remove_avatar,
title: Messages.profile_remove_avatar
});
$avatar.append(delButton);
$(delButton).click(() => {
const old = APP.profileData?.avatar;
APP.profile.execCommand("SET", {
key: 'avatar',
value: ""
}, (newData) => {
APP.profileData = newData;
sframeChan.query("Q_PROFILE_AVATAR_REMOVE", old,
(err, err2) => {
if (err || err2) {
return void UI.warn(err || err2);
}
onProfileEvt.fire();
});
});
});
}, void 0, badge);
};
makeBlock('profile-avatar', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const avatar = h('div.cp-avatar');
const $avatar = $(avatar);
redrawAvatar($avatar);
onProfileEvt.reg(() => {
redrawAvatar($avatar);
});
// Upload
const data = MT.addAvatar(common, (ev, data) => {
const old = APP.profileData?.avatar;
const todo = () => {
APP.profile.execCommand("SET", {
key: 'avatar',
value: data.url
}, (newData) => {
console.error(newData?.avatar, newData);
sframeChan.query("Q_PROFILE_AVATAR_ADD",
data.url, (err, err2) => {
if (err || err2) {
return void UI.warn(err || err2);
}
APP.profileData = newData;
onProfileEvt.fire();
});
});
};
if (old) {
sframeChan.query("Q_PROFILE_AVATAR_REMOVE",
old, (err, err2) => {
if (err || err2) {
return void UI.warn(err || err2);
}
todo();
});
return;
}
todo();
});
const $upButton = common.createButton('upload', false, data);
$upButton.removeClass('btn-primary').addClass('btn-secondary');
$upButton.removeProp('title');
$upButton.text(Messages.profile_upload);
$upButton.prepend($('<i>', {'class': 'fa fa-upload', 'aria-hidden': 'true'}));
cb([
h('label', Messages.settings_profileAvatarLabel),
h('div.cp-settings-avatar-container', avatar),
$upButton[0]
]);
}, false, true);
makeBlock('profile-description', function(cb) {
if (!common.isLoggedIn()) { return cb(false); }
const input = APP.blocks.textarea();
const button = APP.blocks.button('primary', '',
Messages.settings_save);
const labelled = APP.blocks.labelledInput(
Messages.profile_editDescription, input);
$(labelled).append(button);
$(input).val(APP.profileData?.description || '');
const cm = SFCodeMirror.create("gfm", CodeMirror, input);
const editor = APP.editor = cm.editor;
editor.setOption('lineNumbers', true);
editor.setOption('lineWrapping', true);
editor.setOption('styleActiveLine', true);
editor.setOption('readOnly', false);
cm.configureTheme(common, function () {});
editor.setOption("extraKeys", {
"Esc": function () {
$(button).focus();
}
});
editor.refresh();
Util.onClickEnter($(button), () => {
const value = editor.getValue();
APP.profile.execCommand('SET', {
key: 'description',
value
}, function (data) {
UI.log(Messages.saved);
APP.profileData = data;
onProfileEvt.fire();
});
});
const markdownTb = common.createMarkdownToolbar(editor, {
toggleBar: true
});
$(input).before(markdownTb.toggleButton);
$(input).before(markdownTb.toolbar);
onProfileEvt.reg(() => {
editor.setValue(APP.profileData?.description || '');
editor.save();
editor.refresh();
});
cb(labelled);
}, false, true);
// Settings app
var createUsageButton = function() {
common.createUsageBar(null, 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 SIDEBAR_ICONS = {
account: 'fa fa-user-o',
profile: 'fa fa-user-circle',
drive: 'fa fa-hdd-o',
cursor: 'fa fa-i-cursor',
code: 'fa fa-file-code-o',
pad: 'cptools cptools-richtext',
security: 'fa fa-lock',
kanban: 'cptools cptools-kanban',
style: 'cptools cptools-palette',
notifications: 'fa fa-bell'
};
let SIDEBAR_NAMES = {}; // for extension points
Messages.settings_cat_notifications = Messages.notificationsPage;
Messages.settings_cat_profile = Messages.profileButton;
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';
if (!categories[active]) { active = 'account'; }
Object.keys(categories).forEach(function(key) {
var iconClass = SIDEBAR_ICONS[key];
var icon;
if (iconClass) {
icon = h('span', {
class: iconClass,
});
}
let name = SIDEBAR_NAMES[key] ||
Messages['settings_cat_' + key] || key;
var $category = $(h('div.cp-sidebarlayout-category', {
'tabindex': 0,
'data-category': key
}, [
icon,
name,
])).appendTo($categories);
if (key === active) {
$category.addClass('cp-leftside-active');
}
$category.on('click keypress', function (event) {
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
if (!Array.isArray(categories[key]) && categories[key].onClick) {
categories[key].onClick();
return;
}
active = key;
common.setHash(key);
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
$category.addClass('cp-leftside-active');
showCategories(categories[key]);
}
});
});
showCategories(categories[active]);
common.setHash(active);
};
var onProfileEvent = function (obj) {
var ev = obj.ev;
var data = obj.data;
if (ev === 'UPDATE') {
APP.profileData = data;
onProfileEvt.fire();
return;
}
};
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) {
APP.profile = common.makeUniversal('profile', {
onEvent: onProfileEvent
});
APP.badge = common.makeUniversal('badge', {
onEvent: onProfileEvent
});
APP.profile.execCommand('SUBSCRIBE', null, waitFor(obj => {
APP.profileData = obj;
}));
}).nThen(function( /*waitFor*/ ) {
metadataMgr = common.getMetadataMgr();
privateData = metadataMgr.getPrivateData();
// Toolbar
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
var configTb = {
displayed: displayed,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.settings_title,
metadataMgr: common.getMetadataMgr(),
skipLink: '#cp-sidebarlayout-leftside'
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
APP.history = common.makeUniversal('history');
// EXTENSION_POINT:SETTINGS_CATEGORY
common.getExtensionsSync('SETTINGS_CATEGORY').forEach(ext => {
if (!ext || !ext.id || !ext.name || !ext.getContent) {
return console.error('Invalid extension point', 'SETTINGS_CATEGORY', ext);
}
if (categories[ext.id]) {
return console.error('Extension point ID already used', ext);
}
SIDEBAR_ICONS[ext.id] = ext.icon;
SIDEBAR_NAMES[ext.id] = ext.name;
categories[ext.id] = ext.getContent(common);
});
APP.blocks = Sidebar.blocks('settings', common);
// 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();
common.setTabTitle(Messages.settings_title);
UI.removeLoadingScreen();
});
});