diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 5640e014b..ef6e1189a 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -10,7 +10,7 @@ .sidebar-layout_main() { - input[type="text"] { + input[type="text"], input[type="password"] { padding-left: 10px; } #cp-sidebarlayout-container { @@ -60,7 +60,7 @@ } margin-bottom: 20px; } - [type="text"], button { + [type="text"], [type="password"], button { vertical-align: middle; height: 40px; box-sizing: border-box; diff --git a/customize.dist/src/less2/include/toolbar-history.less b/customize.dist/src/less2/include/toolbar-history.less index abf14f3ac..31da75ad1 100644 --- a/customize.dist/src/less2/include/toolbar-history.less +++ b/customize.dist/src/less2/include/toolbar-history.less @@ -5,23 +5,47 @@ display: none; text-align: center; width: 100%; + padding: 10px 0; + align-items: center; + justify-content: center; * { font: @colortheme_app-font; } - .cp-toolbar-history-next { - display: inline-block; - vertical-align: middle; - margin: 20px; + .cp-history-filler { + flex: 1; } - .cp-toolbar-history-previous { - display: inline-block; - vertical-align: middle; - margin: 20px; + .cp-toolbar-history-close, + .cp-toolbar-history-revert { + background: white; + color: black; + //margin-top: 5px; + &:hover { + background-color: #e6e6e6; + } + } + .cp-toolbar-history-loadmore { + height: 100%; + color: black; + width: 25px; + position: absolute; + left: 0; + padding: 0; + } + .cp-toolbar-history-version { + position: absolute; + height: 25px; + line-height: 25px; + width: 100%; + text-align: center; } .cp-toolbar-history-goto { display: inline-block; vertical-align: middle; text-align: center; + flex: 1; + flex-basis: 80%; + min-width: 0; + max-width: 600px; input { width: 75px; } } .cp-toolbar-history-goto-input { @@ -29,6 +53,30 @@ margin-left: 5px; vertical-align: middle; } + .cp-toolbar-history-bar { + width: 100%; + background: white; + height: 25px; + margin: auto; + position: relative; + } + .cp-toolbar-history-pos-container { + width: ~"calc(100% - 2px)"; + height: 25px; + position: relative; + } + @pos-color: #55FF55; + .cp-toolbar-history-pos { + width: 2px; + height: 25px; + background: @pos-color; + &:after { + content: ''; + border: 6px solid transparent; + border-top-color: @pos-color; + margin-left: -5px; + } + } button { color: inherit; background-color: rgba(0,0,0,0.2); @@ -36,14 +84,6 @@ background-color: rgba(0,0,0,0.4); } } - .cp-toolbar-history-close { - background: white; - color: black; - margin-top: 5px; - &:hover { - background-color: #e6e6e6; - } - } .fa-spinner { font-size: 66px; } diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 838dfcd2b..a936f592c 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -232,12 +232,11 @@ define(function () { out.historyText = "Historique"; out.historyButton = "Afficher l'historique du document"; - out.history_next = "Voir la version suivante"; - out.history_prev = "Voir la version précédente"; - out.history_goTo = "Voir la version sélectionnée"; + out.history_next = "Version plus récente"; + out.history_prev = "Version plus ancienne"; + out.history_loadMore = "Charger davantage d'historique"; out.history_close = "Retour"; out.history_closeTitle = "Fermer l'historique"; - out.history_restore = "Restaurer"; out.history_restoreTitle = "Restaurer la version du document sélectionnée"; out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?"; out.history_restoreDone = "Document restauré"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 16e837a35..008d53217 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -234,12 +234,10 @@ define(function () { out.historyText = "History"; out.historyButton = "Display the document history"; - out.history_next = "Go to the next version"; - out.history_prev = "Go to the previous version"; - out.history_goTo = "Go to the selected version"; - out.history_close = "Back"; + out.history_next = "Newer version"; + out.history_prev = "Older version"; + out.history_loadMore = "Load more history"; out.history_closeTitle = "Close the history"; - out.history_restore = "Restore"; out.history_restoreTitle = "Restore the selected version of the document"; out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?"; out.history_restoreDone = "Document restored"; @@ -602,6 +600,15 @@ define(function () { out.settings_templateSkip = "Skip the template selection modal"; out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template."; + out.settings_changePasswordTitle = "Change your password"; // XXX + out.settings_changePasswordHint = "Change your account's password without losing its data. You have to enter your existing password once, and the new password you want twice." + + "We can't reset your password if you forget it so be very careful!"; // XXX + out.settings_changePasswordButton = "Change password"; // XXX + out.settings_changePasswordCurrent = "Existing password"; // XXX + out.settings_changePasswordNew = "New password"; // XXX + out.settings_changePasswordNewConfirm = "Confirm new password"; // XXX + out.settings_changePasswordConfirm = "Are you sure?"; // XXX + out.upload_title = "File upload"; out.upload_modal_title = "File upload options"; out.upload_modal_filename = "File name (extension {0} added automatically)"; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index dd7a46d25..bbd0a9428 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -475,7 +475,7 @@ define([ if (typeof(meta) === "object") { meta.defaultTitle = meta.title || meta.defaultTitle; delete meta.users; - delete meta.title; + meta.title = ""; } val = JSON.stringify(parsed); } catch (e) { @@ -504,6 +504,13 @@ define([ if (typeof (data.title) !== "string") { return cb('Missing title'); } if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); } + if (common.initialPath) { + if (!data.path) { + data.path = common.initialPath; + delete common.initialPath; + } + } + postMessage("SET_PAD_TITLE", data, function (obj) { if (obj && obj.error) { console.log("unable to set pad title"); @@ -699,6 +706,9 @@ define([ common.getFullHistory = function (data, cb) { postMessage("GET_FULL_HISTORY", data, cb); }; + common.getHistoryRange = function (data, cb) { + postMessage("GET_HISTORY_RANGE", data, cb); + }; common.getShareHashes = function (secret, cb) { var hashes; @@ -935,7 +945,7 @@ define([ driveEvents: rdyCfg.driveEvents // Boolean }; if (sessionStorage[Constants.newPadPathKey]) { - cfg.initialPath = sessionStorage[Constants.newPadPathKey]; + common.initialPath = sessionStorage[Constants.newPadPathKey]; delete sessionStorage[Constants.newPadPathKey]; } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e22e2d574..890ddb19a 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1110,10 +1110,13 @@ define([ } }; var msgs = []; + var completed = false; var onMsg = function (msg) { + if (completed) { return; } var parsed = parse(msg); if (parsed[0] === 'FULL_HISTORY_END') { cb(msgs); + completed = true; return; } if (parsed[0] !== 'FULL_HISTORY') { return; } @@ -1132,6 +1135,63 @@ define([ network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey])); }; + Store.getHistoryRange = function (clientId, data, cb) { + var network = store.network; + var hkn = network.historyKeeper; + var parse = function (msg) { + try { + return JSON.parse(msg); + } catch (e) { + return null; + } + }; + var msgs = []; + var first = true; + var fullHistory = false; + var completed = false; + var lastKnownHash; + var txid = Util.uid(); + + var onMsg = function (msg) { + if (completed) { return; } + var parsed = parse(msg); + if (parsed[1] !== txid) { console.log('bad txid'); return; } + if (parsed[0] === 'HISTORY_RANGE_END') { + cb({ + messages: msgs, + isFull: fullHistory, + lastKnownHash: lastKnownHash + }); + completed = true; + return; + } + if (parsed[0] !== 'HISTORY_RANGE') { return; } + if (parsed[2] && parsed[1].validateKey) { // Metadata + return; + } + if (parsed[2][3] !== data.channel) { return; } + msg = parsed[2][4]; + if (msg) { + if (first) { + // If the first message if not a checkpoint, it means it is the first + // message of the pad, so we have the full history! + if (!/^cp\|/.test(msg)) { fullHistory = true; } + lastKnownHash = msg.slice(0,64); + first = false; + } + msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, ''); + msgs.push(msg); + } + }; + + network.on('message', onMsg); + network.sendto(hkn, JSON.stringify(['GET_HISTORY_RANGE', data.channel, { + from: data.lastKnownHash, + cpCount: 2, + txid: txid + }])); + }; + // Drive Store.userObjectCommand = function (clientId, cmdData, cb) { if (!cmdData || !cmdData.cmd) { return; } diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index e1a941040..2ed424610 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -71,6 +71,7 @@ define([ JOIN_PAD: Store.joinPad, LEAVE_PAD: Store.leavePad, GET_FULL_HISTORY: Store.getFullHistory, + GET_HISTORY_RANGE: Store.getHistoryRange, IS_NEW_CHANNEL: Store.isNewChannel, // Drive DRIVE_USEROBJECT: Store.userObjectCommand, diff --git a/www/common/sframe-common-history.js b/www/common/sframe-common-history.js index 27503d07a..efaa01cc1 100644 --- a/www/common/sframe-common-history.js +++ b/www/common/sframe-common-history.js @@ -1,26 +1,36 @@ define([ 'jquery', '/common/common-interface.js', + '/bower_components/nthen/index.js', //'/bower_components/chainpad-json-validator/json-ot.js', '/bower_components/chainpad/chainpad.dist.js', -], function ($, UI, ChainPad /* JsonOT */) { +], function ($, UI, nThen, ChainPad /* JsonOT */) { //var ChainPad = window.ChainPad; var History = {}; - var getStates = function (rt) { - var states = []; - var b = rt.getAuthBlock(); - if (b) { states.unshift(b); } - while (b.getParent()) { - b = b.getParent(); - states.unshift(b); + History.create = function (common, config) { + if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");} + if (History.loading) { return void console.error("History is already being loaded..."); } + History.loading = true; + var $toolbar = config.$toolbar; + + if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) { + throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory"); } - return states; - }; - var loadHistory = function (config, common, cb) { - var createRealtime = function () { + var getStates = function (rt) { + var states = []; + var b = rt.getAuthBlock(); + if (b) { states.unshift(b); } + while (b.getParent()) { + b = b.getParent(); + states.unshift(b); + } + return states; + }; + + var createRealtime = function (config) { return ChainPad.create({ userName: 'history', validateContent: function (content) { @@ -33,36 +43,45 @@ define([ } }, initialState: '', - //patchTransformer: ChainPad.NaiveJSONTransformer, - //logLevel: 0, - //transformFunction: JsonOT.validate, logLevel: config.debug ? 2 : 0, noPrune: true }); }; - var realtime = createRealtime(); - - History.readOnly = common.getMetadataMgr().getPrivateData().readOnly; - - /*var to = window.setTimeout(function () { - cb('[GET_FULL_HISTORY_TIMEOUT]'); - }, 30000);*/ - common.getFullHistory(realtime, function () { - //window.clearTimeout(to); - cb(null, realtime); - }); - }; + var loadFullHistory = function (config, common, cb) { + var realtime = createRealtime(config); + common.getFullHistory(realtime, function () { + cb(null, realtime); + }); + }; + loadFullHistory = loadFullHistory; - History.create = function (common, config) { - if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");} - if (History.loading) { return void console.error("History is already being loaded..."); } - History.loading = true; - var $toolbar = config.$toolbar; + var fillChainPad = function (realtime, messages) { + messages.forEach(function (m) { + realtime.message(m); + }); + }; - if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) { - throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory"); - } + var allMessages = []; + var lastKnownHash; + var isComplete = false; + var loadMoreHistory = function (config, common, cb) { + if (isComplete) { return void cb ('EFULL'); } + var realtime = createRealtime(config); + var sframeChan = common.getSframeChannel(); + + sframeChan.query('Q_GET_HISTORY_RANGE', { + lastKnownHash: lastKnownHash + }, function (err, data) { + if (err) { return void console.error(err); } + if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); } + lastKnownHash = data.lastKnownHash; + isComplete = data.isFull; + Array.prototype.unshift.apply(allMessages, data.messages); // Destructive concat + fillChainPad(realtime, allMessages); + cb (null, realtime); + }); + }; // config.setHistory(bool, bool) // - bool1: history value @@ -84,21 +103,20 @@ define([ }; config.setHistory(true); - var onReady = function () { }; var Messages = common.Messages; var realtime; var states = []; - var c = states.length - 1; + var c = 0;//states.length - 1; var $hist = $toolbar.find('.cp-toolbar-history'); var $left = $toolbar.find('.cp-toolbar-leftside'); var $right = $toolbar.find('.cp-toolbar-rightside'); var $cke = $toolbar.find('.cke_toolbox_main'); - $hist.html('').show(); + $hist.html('').css('display', 'flex'); $left.hide(); $right.hide(); $cke.hide(); @@ -107,29 +125,73 @@ define([ var onUpdate; - var update = function () { + var update = function (newRt) { + realtime = newRt; if (!realtime) { return []; } states = getStates(realtime); if (typeof onUpdate === "function") { onUpdate(); } return states; }; + var $loadMore, $version, get; + // Get the content of the selected version, and change the version number - var get = function (i) { + var loading = false; + var loadMore = function (cb) { + if (loading) { return; } + loading = true; + $loadMore.removeClass('fa fa-ellipsis-h') + .append($('', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'})); + loadMoreHistory(config, common, function (err, newRt) { + if (err === 'EFULL') { + $loadMore.off('click').hide(); + get(c); + $version.show(); + return; + } + loading = false; + if (err) { return void console.error(err); } + update(newRt); + $loadMore.addClass('fa fa-ellipsis-h').html(''); + get(c); + if (cb) { cb(); } + }); + }; + get = function (i) { i = parseInt(i); if (isNaN(i)) { return; } - if (i < 0) { i = 0; } - if (i > states.length - 1) { i = states.length - 1; } - var val = states[i].getContent().doc; + if (i > 0) { i = 0; } + if (i < -(states.length - 2)) { i = -(states.length - 2); } + if (i <= -(states.length - 11)) { + loadMore(); + } + var idx = states.length - 1 + i; + var val = states[idx].getContent().doc; c = i; if (typeof onUpdate === "function") { onUpdate(); } - $hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous').css('visibility', ''); - if (c === states.length - 1) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); } - if (c === 0) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); } + $hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' + + '.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous') + .css('visibility', ''); + if (c === -(states.length-1)) { + $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); + $hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden'); + } + if (c === 0) { + $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); + $hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden'); + } + var $pos = $hist.find('.cp-toolbar-history-pos'); + var p = 100 * (1 - (-c / (states.length-1))); + $pos.css('margin-left', p+'%'); + + // Display the version when the full history is loaded + // Note: the first version is always empty and probably can't be displayed, so + // we can consider we have only states.length - 1 versions + $version.text(idx + ' / ' + (states.length-1)); if (config.debug) { - console.log(states[i]); - var ops = states[i] && states[i].getPatch() && states[i].getPatch().operations; + console.log(states[idx]); + var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations; if (Array.isArray(ops)) { ops.forEach(function (op) { console.log(op); }); } @@ -148,6 +210,17 @@ define([ // Create the history toolbar var display = function () { $hist.html(''); + + var $rev = $('', { + 'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o', + title: Messages.history_restoreTitle + }).appendTo($hist);//.text(Messages.history_restore); + if (History.readOnly) { $rev.css('visibility', 'hidden'); } + $('', {'class': 'cp-history-filler'}).appendTo($hist); + var $fastPrev = $('', { + 'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary', + title: Messages.history_prev + }).appendTo($hist); var $prev =$('', { 'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary', title: Messages.history_prev @@ -157,58 +230,73 @@ define([ 'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary', title: Messages.history_next }).appendTo($hist); + var $fastNext = $('', { + 'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary', + title: Messages.history_next + }).appendTo($hist); + $('', {'class': 'cp-history-filler'}).appendTo($hist); + var $close = $('', { + 'class':'cp-toolbar-history-close fa fa-window-close', + title: Messages.history_closeTitle + }).appendTo($hist); + + var $bar = $('', {'class': 'cp-toolbar-history-bar'}).appendTo($nav); + var $container = $('', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar); + $('', {'class': 'cp-toolbar-history-pos'}).appendTo($container); + + $version = $('', { + 'class': 'cp-toolbar-history-version' + }).prependTo($bar).hide(); + $loadMore = $('', { + 'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h', + title: Messages.history_loadMore + }).click(function () { + loadMore(function () { + get(c); + }); + }).prependTo($container); - $('').text(Messages.history_version).appendTo($nav); - var $cur = $('', { - 'class' : 'cp-toolbar-history-goto-input', - 'type' : 'number', - 'min' : '1', - 'max' : states.length - }).val(c + 1).appendTo($nav).mousedown(function (e) { - // stopPropagation because the event would be cancelled by the dropdown menus + // Load a version when clicking on the bar + $container.click(function (e) { e.stopPropagation(); + if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; } + var p = e.offsetX / $container.width(); + var v = -Math.round((states.length - 1) * (1 - p)); + render(get(v)); }); - var $label2 = $('').text(' / '+ states.length).appendTo($nav); - $('').appendTo($nav); - var $close = $('', { - 'class':'cp-toolbar-history-close', - title: Messages.history_closeTitle - }).text(Messages.history_closeTitle).appendTo($nav); - var $rev = $('', { - 'class':'cp-toolbar-history-revert buttonSuccess', - title: Messages.history_restoreTitle - }).text(Messages.history_restore).appendTo($nav); - if (History.readOnly) { $rev.hide(); } onUpdate = function () { - $cur.attr('max', states.length); - $cur.val(c+1); - $label2.text(' / ' + states.length); + // Called when a new version is loaded }; + var onKeyDown, onKeyUp; var close = function () { $hist.hide(); $left.show(); $right.show(); $cke.show(); $(window).trigger('resize'); + $(window).off('keydown', onKeyDown); + $(window).off('keyup', onKeyUp); }; - // Buttons actions + // Version buttons $prev.click(function () { render(getPrevious()); }); $next.click(function () { render(getNext()); }); - $cur.keydown(function (e) { + $fastPrev.click(function () { render(getPrevious(10)); }); + $fastNext.click(function () { render(getNext(10)); }); + onKeyDown = function (e) { var p = function () { e.preventDefault(); }; - if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right if (e.which === 33) { p(); return render(getNext(10)); } // PageUp if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp if (e.which === 27) { p(); $close.click(); } - }).keyup(function (e) { e.stopPropagation(); }).focus(); - $cur.on('change', function () { - render( get($cur.val() - 1) ); - }); + }; + onKeyUp = function (e) { e.stopPropagation(); }; + $(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus(); + + // Close & restore buttons $close.click(function () { states = []; close(); @@ -229,14 +317,13 @@ define([ }; // Load all the history messages into a new chainpad object - loadHistory(config, common, function (err, newRt) { + loadMoreHistory(config, common, function (err, newRt) { + History.readOnly = common.getMetadataMgr().getPrivateData().readOnly; History.loading = false; if (err) { throw new Error(err); } - realtime = newRt; - update(); + update(newRt); c = states.length - 1; display(); - onReady(); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index c40bff5e3..84aaa1b26 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -420,6 +420,24 @@ define([ })); }); }); + sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) { + var crypto = Crypto.createEncryptor(secret.keys); + Cryptpad.getHistoryRange({ + channel: secret.channel, + validateKey: secret.keys.validateKey, + lastKnownHash: data.lastKnownHash + }, function (data) { + cb({ + isFull: data.isFull, + messages: data.messages.map(function (msg) { + // The 3rd parameter "true" means we're going to skip signature validation. + // We don't need it since the message is already validated serverside by hk + return crypto.decrypt(msg, true, true); + }), + lastKnownHash: data.lastKnownHash + }); + }); + }); sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) { var href; diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 8bb68aeef..fc77676d2 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -90,6 +90,7 @@ define({ // Request the full history from the server when the users clicks on the history button. // Callback is called when the FULL_HISTORY_END message is received in the outside. 'Q_GET_FULL_HISTORY': true, + 'Q_GET_HISTORY_RANGE': true, // When a (full) history message is received from the server. 'EV_RT_HIST_MESSAGE': true, diff --git a/www/settings/app-settings.less b/www/settings/app-settings.less index a47dd88f6..2cfd48a65 100644 --- a/www/settings/app-settings.less +++ b/www/settings/app-settings.less @@ -55,6 +55,15 @@ width: @sidebar_button-width; } } + .cp-settings-change-password { + [type="password"], [type="text"] { + width: @sidebar_button-width; + flex: unset; + } + button { + margin-top: 5px; + } + } .cp-settings-drive-backup { button { span.fa { diff --git a/www/settings/inner.js b/www/settings/inner.js index 3d59acb9c..8fd852858 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -9,6 +9,7 @@ define([ '/common/common-hash.js', '/customize/messages.js', '/common/hyperscript.js', + '/customize/credential.js', '/customize/application_config.js', '/api/config', '/common/outer/login-block.js', // XXX HACK @@ -28,6 +29,7 @@ define([ Hash, Messages, h, + Cred, AppConfig, ApiConfig, Block // XXX HACK @@ -377,13 +379,32 @@ define([ var $div = $('', { 'class': 'cp-settings-change-password cp-sidebarlayout-element'}); - $('', {'class': 'label'}).text("TODO Change your password").appendTo($div); // XXX + $('', {'class': 'label'}).text(Messages.settings_changePasswordTitle).appendTo($div); $('', {'class': 'cp-sidebarlayout-description'}) - .append("TODO").appendTo($div); // XXX + .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_WRITE_LOGIN_BLOCK', data, function (err, obj) { if (err || obj.error) { return void cb ({error: err || obj.error}); } @@ -422,6 +443,53 @@ define([ }); } + 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; + } + + UI.confirm(Messages.settings_changePasswordConfirm, + function (yes) { + if (!yes) { return; } + // TODO + console.log(oldPassword, newPassword, newPasswordConfirm); + }, { + 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; };