diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 3af6aad8a..1db0b64ed 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -290,14 +290,7 @@ button.primary:hover{ ].join(''); var built = false; - // XXX var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end']; - Messages.loading_state_0 = "Less"; - Messages.loading_state_1 = "Drive"; - Messages.loading_state_2 = "Migrate"; - Messages.loading_state_3 = "SF"; - Messages.loading_state_4 = "Team"; - Messages.loading_state_5 = "Pad"; var current; var makeList = function (data) { var c = types.indexOf(data.type); diff --git a/customize.dist/login.js b/customize.dist/login.js index 35121f113..d07d4c4c3 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -28,6 +28,19 @@ define([ }; var Nacl = window.nacl; + + var redirectTo = '/drive/'; + var setRedirectTo = function () { + var parsed = Hash.parsePadUrl(window.location.href); + if (parsed.hashData && parsed.hashData.newPadOpts) { + var newPad = Hash.decodeDataOptions(parsed.hashData.newPadOpts); + redirectTo = newPad.href; + } + }; + if (window.location.hash) { + setRedirectTo(); + } + var allocateBytes = Exports.allocateBytes = function (bytes) { var dispense = Cred.dispenser(bytes); @@ -118,11 +131,11 @@ define([ }; var setMergeAnonDrive = function () { - sessionStorage.migrateAnonDrive = 1; + Exports.mergeAnonDrive = 1; }; var setCreateReadme = function () { - sessionStorage.createReadme = 1; + Exports.createReadme = 1; }; Exports.loginOrRegister = function (uname, passwd, isRegister, shouldImport, cb) { @@ -416,12 +429,20 @@ define([ }); }; Exports.redirect = function () { - if (sessionStorage.redirectTo) { - var h = sessionStorage.redirectTo; + if (redirectTo) { + var h = redirectTo; + var loginOpts = {}; + if (Exports.mergeAnonDrive) { + loginOpts.mergeAnonDrive = 1; + } + if (Exports.createReadme) { + loginOpts.createReadme = 1; + } + h = Hash.getLoginURL(h, loginOpts); + var parser = document.createElement('a'); parser.href = h; if (parser.origin === window.location.origin) { - delete sessionStorage.redirectTo; window.location.href = h; return; } diff --git a/customize.dist/pages/features.js b/customize.dist/pages/features.js index 26303f9ba..da4a44c55 100644 --- a/customize.dist/pages/features.js +++ b/customize.dist/pages/features.js @@ -68,14 +68,6 @@ define([ target: '_blank', rel: 'noopener noreferrer' }, h('button.cp-features-register-button', Msg.features_f_subscribe)); - /*$(premiumButton).click(function (e) { - if (LocalStore.isLoggedIn()) { return; } - // Not logged in: go to /login with a redirect to this page - e.preventDefault(); - e.stopPropagation(); - sessionStorage.redirectTo = '/features.html'; - window.location.href = '/login/'; - });*/ var anonymousFeatures = h('div.col-12.col-sm-4.cp-anon-user',[ diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js index 5a4ec72eb..cd81d1b7c 100644 --- a/customize.dist/pages/index.js +++ b/customize.dist/pages/index.js @@ -4,12 +4,13 @@ define([ '/common/hyperscript.js', '/common/common-feedback.js', '/common/common-interface.js', + '/common/common-hash.js', '/common/textFit.min.js', '/customize/messages.js', '/customize/application_config.js', '/common/outer/local-store.js', '/customize/pages.js' -], function ($, Config, h, Feedback, UI, TextFit, Msg, AppConfig, LocalStore, Pages) { +], function ($, Config, h, Feedback, UI, Hash, TextFit, Msg, AppConfig, LocalStore, Pages) { var urlArgs = Config.requireConf.urlArgs; var isAvailableType = function (x) { @@ -46,8 +47,9 @@ define([ var href = '/'+ x[0] +'/'; var attr = isEnabled ? { href: href } : { onclick: function () { - sessionStorage.redirectTo = href; - window.location.href = '/login/'; + var href = Hash.hashToHref('', 'login'); + var url = Hash.getNewPadURL(href, { href: href }); + window.location.href = url; } }; if (!isEnabled) { diff --git a/customize.dist/src/less2/include/app-print.less b/customize.dist/src/less2/include/app-print.less index e4c344b20..7f42f100b 100644 --- a/customize.dist/src/less2/include/app-print.less +++ b/customize.dist/src/less2/include/app-print.less @@ -9,10 +9,6 @@ max-height: none; overflow: visible; display: block; - @page { - margin: 0; - size: landscape; - } // Slide app body.cp-app-slide { display: block; @@ -48,11 +44,15 @@ // Code app body.cp-app-code { display: block; + height: auto; * { visibility: hidden; height: auto; max-height: none; } + .cp-toolbar-userlist-drawer { + display: none; + } #cme_toolbox { display: none; } @@ -64,6 +64,7 @@ #cp-app-code-preview { display: block; #cp-app-code-print { + font-size: 20px; display: block; overflow: visible !important; width: 100%; diff --git a/customize.dist/src/print-landscape.css b/customize.dist/src/print-landscape.css new file mode 100644 index 000000000..26a9d495e --- /dev/null +++ b/customize.dist/src/print-landscape.css @@ -0,0 +1,5 @@ +@page { + margin: 0; + size: A4 landscape; +} + diff --git a/customize.dist/src/print.css b/customize.dist/src/print.css new file mode 100644 index 000000000..1baf803d4 --- /dev/null +++ b/customize.dist/src/print.css @@ -0,0 +1,4 @@ +@page { + margin: 3cm; + size: A4 portrait; +} diff --git a/www/auth/index.html b/www/auth/index.html deleted file mode 100644 index 685ca37c4..000000000 --- a/www/auth/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/www/auth/main.js b/www/auth/main.js deleted file mode 100644 index fcbeaf3af..000000000 --- a/www/auth/main.js +++ /dev/null @@ -1,193 +0,0 @@ -define([ - 'jquery', - '/api/config', - '/common/cryptget.js', - '/common/pinpad.js', - '/common/common-constants.js', - '/common/common-hash.js', - '/common/outer/local-store.js', - '/common/outer/login-block.js', - '/common/outer/network-config.js', - '/customize/login.js', - '/common/test.js', - '/bower_components/nthen/index.js', - '/bower_components/netflux-websocket/netflux-client.js', - '/bower_components/tweetnacl/nacl-fast.min.js' -], function ($, ApiConfig, Crypt, Pinpad, Constants, Hash, LocalStore, Block, NetConfig, Login, Test, nThen, Netflux) { - var Nacl = window.nacl; - - var signMsg = function (msg, privKey) { - var signKey = Nacl.util.decodeBase64(privKey); - var buffer = Nacl.util.decodeUTF8(msg); - return Nacl.util.encodeBase64(Nacl.sign(buffer, signKey)); - }; - - // TODO: Allow authing for any domain as long as the user clicks an "accept" button - // inside of the iframe. - var AUTHORIZED_DOMAINS = [ - /\.cryptpad\.fr$/, - /^http(s)?:\/\/localhost\:/ - ]; - - // Safari is weird about localStorage in iframes but seems to let sessionStorage slide. - localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] || - sessionStorage[Constants.userHashKey]; - - var proxy; - var rpc; - var network; - var rpcError; - var contacts = {}; - - var loadProxy = function (hash) { - nThen(function (waitFor) { - var wsUrl = NetConfig.getWebsocketURL(); - var w = waitFor(); - Netflux.connect(wsUrl).then(function (_network) { - network = _network; - w(); - }, function (err) { - rpcError = err; - console.error(err); - }); - }).nThen(function (waitFor) { - Crypt.get(hash, waitFor(function (err, val) { - if (err) { - waitFor.abort(); - console.error(err); - return; - } - try { - var parsed = JSON.parse(val); - proxy = parsed; - } catch (e) { - console.log("Can't parse user drive", e); - } - }), { - network: network - }); - }).nThen(function () { - var origin = ApiConfig.fileHost || window.location.origin; - // Get contacts and extract their avatar channel and key - var getData = function (obj, href) { - var parsed = Hash.parsePadUrl(href); - if (!parsed || parsed.type !== "file") { return; } - var secret = Hash.getSecrets('file', parsed.hash); - if (!secret.keys || !secret.channel) { return; } - obj.avatarKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); - obj.avatarSrc = origin + Hash.getBlobPathFromHex(secret.channel); - }; - contacts.teams = proxy.teams || {}; - contacts.friends = proxy.friends || {}; - Object.keys(contacts.friends).map(function (key) { - var friend = contacts.friends[key]; - if (!friend) { return; } - var ret = { - edPublic: friend.edPublic, - name: friend.displayName, - }; - getData(ret, friend.avatar); - contacts.friends[key] = ret; - }); - Object.keys(contacts.teams).map(function (key) { - var team = contacts.teams[key]; - if (!team) { return; } - var avatar = team.metadata && team.metadata.avatar; - var ret = { - edPublic: team.keys && team.keys.drive && team.keys.drive.edPublic, - name: team.metadata && team.metadata.name - }; - getData(ret, avatar); - contacts.teams[key] = ret; - }); - contacts.origin = window.location.origin; - }).nThen(function (waitFor) { - if (!network) { return void waitFor.abort(); } - Pinpad.create(network, proxy, waitFor(function (e, call) { - if (e) { - rpcError = e; - return void waitFor.abort(); - } - rpc = call; - })); - }).nThen(function () { - Test(function () { - // This is only here to maybe trigger an error. - window.drive = proxy['drive']; - Test.passed(); - }); - }); - }; - - var whenReady = function (cb) { - if (proxy && (rpc || rpcError)) { return void cb(); } - console.log('CryptPad not ready...'); - setTimeout(function () { - whenReady(cb); - }, 100); - }; - - $(window).on("message", function (jqe) { - var evt = jqe.originalEvent; - var data = JSON.parse(evt.data); - var domain = evt.origin; - var srcWindow = evt.source; - var ret = { txid: data.txid }; - console.log('CP receiving', data); - if (data.cmd === 'PING') { - ret.res = 'PONG'; - } else if (data.cmd === 'LOGIN') { - Login.loginOrRegister(data.data.name, data.data.password, false, false, function (err) { - if (err) { - ret.error = 'LOGIN_ERROR'; - srcWindow.postMessage(JSON.stringify(ret), domain); - return; - } - loadProxy(LocalStore.getUserHash()); - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - return; - } else if (data.cmd === 'SIGN') { - if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) { - ret.error = "UNAUTH_DOMAIN"; - } else if (!LocalStore.isLoggedIn()) { - ret.error = "NOT_LOGGED_IN"; - } else { - return void whenReady(function () { - var sig = signMsg(data.data, proxy.edPrivate); - ret.res = { - uname: proxy.login_name, - edPublic: proxy.edPublic, - sig: sig - }; - ret.contacts = contacts; - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - } - } else if (data.cmd === 'UPDATE_LIMIT') { - return void whenReady(function () { - if (rpcError) { - // Tell the user on accounts that there was an issue and they need to wait maximum 24h or contact an admin - ret.warning = true; - srcWindow.postMessage(JSON.stringify(ret), domain); - return; - } - rpc.updatePinLimits(function (e, limit, plan, note) { - if (e) { - ret.warning = true; - } - ret.res = [limit, plan, note]; - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - }); - } else { - ret.error = "UNKNOWN_CMD"; - } - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - - var userHash = LocalStore.getUserHash(); - if (userHash) { - loadProxy(userHash); - } -}); diff --git a/www/code/app-code.less b/www/code/app-code.less index 0ca86fd8b..6557d39f6 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -136,7 +136,6 @@ #cp-app-code-print { position: relative; display: none; - margin: 50px; .markdown_preformatted-code; .markdown_gfm-table(black); } diff --git a/www/code/inner.js b/www/code/inner.js index 7dff21c82..31d12eff4 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -42,6 +42,7 @@ define([ 'cm/addon/fold/comment-fold', 'cm/addon/display/placeholder', + 'css!/customize/src/print.css', 'less!/code/app-code.less' ], function ( diff --git a/www/common/boot2.js b/www/common/boot2.js index fc3968fb3..30f776a25 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -43,6 +43,9 @@ define([ console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers"); return void console.log(); } + if (window.CryptPad_loadingError) { + window.CryptPad_loadingError(e); + } throw e; }; diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 553665574..17db2302c 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -5,10 +5,6 @@ define(['/customize/application_config.js'], function (AppConfig) { userNameKey: 'User_name', blockHashKey: 'Block_hash', fileHashKey: 'FS_hash', - // sessionStorage - newPadPathKey: "newPadPath", - newPadTeamKey: "newPadTeam", - newPadFileData: "newPadFileData", // Store displayNameKey: 'cryptpad.username', oldStorageKey: 'CryptPad_RECENTPADS', diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 0b584d4c3..06a1d7fef 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -169,6 +169,28 @@ Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI */ + var getLoginOpts = function (hashArr) { + var k; + // Check if we have a ownerKey for this pad + hashArr.some(function (data) { + if (/^login=/.test(data)) { + k = data.slice(6); + return true; + } + }); + return k || ''; + }; + var getNewPadOpts = function (hashArr) { + var k; + // Check if we have a ownerKey for this pad + hashArr.some(function (data) { + if (/^newpad=/.test(data)) { + k = data.slice(7); + return true; + } + }); + return k || ''; + }; var getVersionHash = function (hashArr) { var k; // Check if we have a ownerKey for this pad @@ -202,21 +224,52 @@ Version 1 parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; parsed.versionHash = getVersionHash(options); + parsed.newPadOpts = getNewPadOpts(options); + parsed.loginOpts = getLoginOpts(options); parsed.ownerKey = getOwnerKey(options); }; + // Version 4: only login or newpad options, smae for all the apps + if (hashArr[1] && hashArr[1] === '4') { + parsed.getHash = function (opts) { + if (!opts || !Object.keys(opts).length) { return ''; } + var hash = '/4/'; + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } + return hash; + }; + parsed.getOptions = function () { + var options = {}; + if (parsed.newPadOpts) { options.newPadOpts = parsed.newPadOpts; } + if (parsed.loginOpts) { options.loginOpts = parsed.loginOpts; } + return options; + }; + + parsed.version = 4; + options = hashArr.slice(2); + addOptions(); + + return parsed; + } + + // The other versions depends on the type if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { parsed.type = 'pad'; - parsed.getHash = function () { return hash; }; + parsed.getHash = function () { + return hash; + }; parsed.getOptions = function () { return { embed: parsed.embed, present: parsed.present, ownerKey: parsed.ownerKey, versionHash: parsed.versionHash, + newPadOpts: parsed.newPadOpts, + loginOpts: parsed.loginOpts, password: parsed.password }; }; + if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 // Old hash parsed.channel = hash.slice(0, 32); @@ -237,6 +290,8 @@ Version 1 if (versionHash) { hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/'; } + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } return hash; }; @@ -284,6 +339,8 @@ Version 1 embed: parsed.embed, present: parsed.present, ownerKey: parsed.ownerKey, + newPadOpts: parsed.newPadOpts, + loginOpts: parsed.loginOpts, password: parsed.password }; }; @@ -295,6 +352,8 @@ Version 1 if (parsed.password || opts.password) { hash += 'p/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } return hash; }; @@ -367,14 +426,26 @@ Version 1 var idx; + // When we start without a hash, use version 4 links to add login or newpad options + var getHash = function (opts) { + if (!opts || !Object.keys(opts).length) { return ''; } + var hash = '/4/'; + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } + return hash; + }; ret.getUrl = function (options) { options = options || {}; var url = '/'; if (!ret.type) { return url; } url += ret.type + '/'; + // New pad with options: append the options to the hash + if (!ret.hashData && options && Object.keys(options).length) { + return url + '#' + getHash(options); + } if (!ret.hashData) { return url; } - if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } - if (ret.hashData.version === 0) { return url + '#' + ret.hash; } + //if (ret.hashData.version === 0) { return url + '#' + ret.hash; } + //if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } var hash = ret.hashData.getHash(options); url += '#' + hash; return url; @@ -523,7 +594,6 @@ Version 1 secret = JSON.parse(JSON.stringify(secret)); if (!secret.keys && !secret.key) { - console.error('e'); return hashes; } else if (!secret.keys) { secret.keys = {}; @@ -575,6 +645,9 @@ Version 1 // Valid hash? if (parsed.hash) { if (!parsed.hashData) { return; } + // New pad: only newPadOpts allowed + if (Object.keys(parsed.hashData).length === 1 && + parsed.hashData.newPadOpts) { return true; } // Version should be a number if (typeof(parsed.hashData.version) === "undefined") { return; } // pads and files should have a base64 (or hex) key @@ -586,6 +659,29 @@ Version 1 return true; }; + Hash.decodeDataOptions = function (opts) { + var b64 = decodeURIComponent(opts); + var str = Nacl.util.encodeUTF8(Nacl.util.decodeBase64(b64)); + return JSON.parse(str); + }; + Hash.encodeDataOptions = function (opts) { + var str = JSON.stringify(opts); + var b64 = Nacl.util.encodeBase64(Nacl.util.decodeUTF8(str)); + return encodeURIComponent(b64); + }; + Hash.getNewPadURL = function (href, opts) { + var parsed = Hash.parsePadUrl(href); + var options = parsed.getOptions(); + options.newPadOpts = Hash.encodeDataOptions(opts); + return parsed.getUrl(options); + }; + Hash.getLoginURL = function (href, opts) { + var parsed = Hash.parsePadUrl(href); + var options = parsed.getOptions(); + options.loginOpts = Hash.encodeDataOptions(opts); + return parsed.getUrl(options); + }; + return Hash; }; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 46faa7f53..83a5e80be 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -281,12 +281,10 @@ define([ var $root = $t.parent(); - Messages.add = "Add"; // XXX - Messages.edit = "Edit"; // XXX var $input = $root.find('.token-input'); var $button = $(h('button.btn.btn-primary', [ h('i.fa.fa-plus'), - h('span', Messages.add) + h('span', Messages.tag_add) ])); @@ -311,7 +309,7 @@ define([ } $form.append($input); $form.append($button); - if (isEdit) { $button.find('span').text(Messages.edit); } + if (isEdit) { $button.find('span').text(Messages.tag_edit); } else { $button.find('span').text(Messages.add); } $container.append($form); $input.focus(); @@ -942,8 +940,6 @@ define([ $loading.removeClass('cp-loading-hidden'); $loading.removeClass('cp-loading-transparent'); if (config.newProgress) { - // XXX re-add progress bar for step 6 after password prompt for PPP - // XXX also burn after reading var progress = h('div.cp-loading-progress', [ h('p.cp-loading-progress-list'), h('p.cp-loading-progress-container') diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index ad8e1ca6e..f9f76429f 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -248,20 +248,15 @@ define([ className: 'secondary', name: Messages.login_register, onClick: function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); } }, { className: 'secondary', name: Messages.login_login, onClick: function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); } - } - ] + }] }; } }; @@ -633,6 +628,7 @@ define([ button .click(common.prepareFeedback(type)) .click(function () { + if (callback) { return void callback(); } UIElements.openTemplatePicker(common, true); }); break; @@ -1802,9 +1798,7 @@ define([ attributes: {'class': 'cp-toolbar-menu-login fa fa-sign-in'}, content: h('span', Messages.login_login), action: function () { - Common.setLoginRedirect(function () { - window.parent.location = origin+'/login/'; - }); + Common.setLoginRedirect('login'); }, }); options.push({ @@ -1812,9 +1806,7 @@ define([ attributes: {'class': 'cp-toolbar-menu-register fa fa-user-plus'}, content: h('span', Messages.login_register), action: function () { - Common.setLoginRedirect(function () { - window.parent.location = origin+'/register/'; - }); + Common.setLoginRedirect('register'); }, }); } @@ -1939,8 +1931,6 @@ define([ $modal.find('.cp-modal').append($title); $modal.find('.cp-modal').append($description); - var $advanced; - var $container = $('
'); var i = 0; var types = AppConfig.availablePadTypes.filter(function (p) { @@ -1964,15 +1954,7 @@ define([ $element.attr('data-type', p); $element.click(function () { $modal.hide(); - if ($advanced && Util.isChecked($advanced)) { - common.sessionStorage.put(Constants.displayPadCreationScreen, true, function (){ - common.openURL('/' + p + '/'); - }); - return; - } - common.sessionStorage.put(Constants.displayPadCreationScreen, "", function () { - common.openURL('/' + p + '/'); - }); + common.openURL('/' + p + '/'); }); }); @@ -1997,12 +1979,6 @@ define([ } return; } - if (e.which === 32 && $advanced) { - $advanced.prop('checked', !$advanced.prop('checked')); - $modal.focus(); - e.stopPropagation(); - e.preventDefault(); - } }); @@ -2109,7 +2085,7 @@ define([ var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); var privateData = metadataMgr.getPrivateData(); - var type = metadataMgr.getMetadataLazy().type; + var type = metadataMgr.getMetadataLazy().type || privateData.app; var fromFileData = privateData.fromFileData; var $body = $('body'); @@ -2284,12 +2260,14 @@ define([ icon: h('span.cptools.cptools-new-template') }); } - allData.unshift({ - name: Messages.creation_noTemplate, - id: 0, - //icon: h('span.fa.fa-file') - icon: UI.getFileIcon({type: type}) - }); + if (!privateData.newTemplate) { + allData.unshift({ + name: Messages.creation_noTemplate, + id: 0, + //icon: h('span.fa.fa-file') + icon: UI.getFileIcon({type: type}) + }); + } var redraw = function (index) { if (index < 0) { i = 0; } else if (index > allData.length - 1) { return; } @@ -2765,13 +2743,8 @@ define([ $(link).click(function (e) { e.preventDefault(); e.stopPropagation(); - if (msg.content.password) { - common.sessionStorage.put('newPadPassword', msg.content.password, function () { - common.openURL(msg.content.href); - }); - return; - } - common.openURL(msg.content.href); + var obj = { pw: msg.content.password || '' }; + common.openURL(Hash.getNewPadURL(msg.content.href, obj)); }); var div = h('div', [ diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index a9b2923a9..36413d89d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -148,6 +148,21 @@ define([ send(); }; + common.setTabHref = function (href) { + var ohc = window.onhashchange; + window.onhashchange = function () {}; + window.location.href = href; + window.onhashchange = ohc; + ohc({reset: true}); + }; + common.setTabHash = function (hash) { + var ohc = window.onhashchange; + window.onhashchange = function () {}; + window.location.hash = hash; + window.onhashchange = ohc; + ohc({reset: true}); + }; + // RESTRICTED // Settings only common.resetDrive = function (cb) { @@ -673,6 +688,7 @@ define([ if (!val) { return void cb('ENOENT'); } + if (data.oo) { return void cb(val); } // OnlyOffice template: are handled in inner try { // Try to fix the title before importing the template var parsed = JSON.parse(val); @@ -1864,8 +1880,9 @@ define([ LocalStore.logout(); // redirect them to log in, and come back when they're done. - sessionStorage.redirectTo = currentPad.href; - window.location.href = '/login/'; + var href = Hash.hashToHref('', 'login'); + var url = Hash.getNewPadURL(href, { href: currentPad.href }); + window.location.href = url; }; common.startAccountDeletion = function (data, cb) { @@ -2089,26 +2106,6 @@ define([ language: common.getLanguage(), driveEvents: true //rdyCfg.driveEvents // Boolean }; - // if a pad is created from a file - if (sessionStorage[Constants.newPadFileData]) { - common.fromFileData = JSON.parse(sessionStorage[Constants.newPadFileData]); - var _parsed1 = Hash.parsePadUrl(common.fromFileData.href); - var _parsed2 = Hash.parsePadUrl(window.location.href); - if (_parsed1.hashData.type === 'pad') { - if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; } - } - delete sessionStorage[Constants.newPadFileData]; - } - - if (sessionStorage[Constants.newPadPathKey]) { - common.initialPath = sessionStorage[Constants.newPadPathKey]; - delete sessionStorage[Constants.newPadPathKey]; - } - - if (sessionStorage[Constants.newPadTeamKey]) { - common.initialTeam = sessionStorage[Constants.newPadTeamKey]; - delete sessionStorage[Constants.newPadTeamKey]; - } var channelIsReady = waitFor(); @@ -2281,12 +2278,6 @@ define([ if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } - /*if (cfg.userHash && sessionStorage) { - // copy User_hash into sessionStorage because cross-domain iframes - // on safari replaces localStorage with sessionStorage or something - sessionStorage.setItem(Constants.userHashKey, cfg.userHash); - }*/ - if (cfg.userHash) { var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); if (localToken === null) { @@ -2341,21 +2332,18 @@ define([ postMessage("DISCONNECT"); }); }).nThen(function (waitFor) { - if (sessionStorage.createReadme) { + if (common.createReadme) { var data = { driveReadme: Messages.driveReadme, driveReadmeTitle: Messages.driveReadmeTitle, }; postMessage("CREATE_README", data, waitFor(function (e) { if (e && e.error) { return void console.error(e.error); } - delete sessionStorage.createReadme; })); } }).nThen(function (waitFor) { - if (sessionStorage.migrateAnonDrive) { - common.mergeAnonDrive(waitFor(function() { - delete sessionStorage.migrateAnonDrive; - })); + if (common.migrateAnonDrive) { + common.mergeAnonDrive(waitFor()); } }).nThen(function (waitFor) { if (AppConfig.afterLogin) { diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index c79e3d4e3..79ad9b83a 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1125,6 +1125,15 @@ define([ var hiddenHref = Hash.hashToHref(hash, parsed.type); window.open(APP.origin + hiddenHref); }; + var openIn = function (type, path, team, fData) { + var obj = { + p: path, + t: team, + d: fData + }; + var href = Hash.hashToHref('', type); + common.openURL(Hash.getNewPadURL(href, obj)); + }; var refresh = APP.refresh = function () { APP.displayDirectory(currentPath); @@ -2667,12 +2676,7 @@ define([ .click(function () { var type = $(this).attr('data-type') || 'pad'; var path = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; - nThen(function (waitFor) { - common.sessionStorage.put(Constants.newPadPathKey, path, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { - common.openURL('/' + type + '/'); - }); + openIn(type, path, APP.team); }); }; var createNewButton = function (isInRoot, $container) { @@ -4227,60 +4231,37 @@ define([ else if ($this.hasClass('cp-app-drive-context-makeacopy')) { if (paths.length !== 1) { return; } el = manager.find(paths[0].path); - var _metadata = manager.getFileData(el); - var _simpleData = { - title: _metadata.filename || _metadata.title, - href: _metadata.href || _metadata.roHref, - password: _metadata.password, - channel: _metadata.channel, - }; - nThen(function (waitFor) { + (function () { var path = currentPath; if (path[0] !== ROOT) { path = [ROOT]; } - common.sessionStorage.put(Constants.newPadFileData, JSON.stringify(_simpleData), waitFor()); - common.sessionStorage.put(Constants.newPadPathKey, path, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { + var _metadata = manager.getFileData(el); + var _simpleData = { + title: _metadata.filename || _metadata.title, + href: _metadata.href || _metadata.roHref, + password: _metadata.password, + channel: _metadata.channel, + }; var parsed = Hash.parsePadUrl(_metadata.href || _metadata.roHref); - common.openURL(Hash.hashToHref('', parsed.type)); - // We need to restore sessionStorage for the next time we want to create a pad from this tab - // NOTE: the 100ms timeout is to fix a race condition in firefox where sessionStorage - // would be deleted before the new tab was created - setTimeout(function () { - common.sessionStorage.put(Constants.newPadFileData, '', function () {}); - common.sessionStorage.put(Constants.newPadPathKey, '', function () {}); - common.sessionStorage.put(Constants.newPadTeamKey, '', function () {}); - }, 100); - }); + openIn(parsed.type, path, APP.team, _simpleData); + })(); } else if ($this.hasClass('cp-app-drive-context-openincode')) { if (paths.length !== 1) { return; } var p = paths[0]; el = manager.find(p.path); - var metadata = manager.getFileData(el); - var simpleData = { - title: metadata.filename || metadata.title, - href: metadata.href, - password: metadata.password, - channel: metadata.channel, - }; - nThen(function (waitFor) { - common.sessionStorage.put(Constants.newPadFileData, JSON.stringify(simpleData), waitFor()); - common.sessionStorage.put(Constants.newPadPathKey, currentPath, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { - common.openURL('/code/'); - // We need to restore sessionStorage for the next time we want to create a pad from this tab - // NOTE: the 100ms timeout is to fix a race condition in firefox where sessionStorage - // would be deleted before the new tab was created - setTimeout(function () { - common.sessionStorage.put(Constants.newPadFileData, '', function () {}); - common.sessionStorage.put(Constants.newPadPathKey, '', function () {}); - common.sessionStorage.put(Constants.newPadTeamKey, '', function () {}); - }, 100); - }); + (function () { + var path = currentPath; + if (path[0] !== ROOT) { path = [ROOT]; } + var _metadata = manager.getFileData(el); + var _simpleData = { + title: _metadata.filename || _metadata.title, + href: _metadata.href || _metadata.roHref, + password: _metadata.password, + channel: _metadata.channel, + }; + openIn('code', path, APP.team, _simpleData); + })(); } - else if ($this.hasClass('cp-app-drive-context-expandall') || $this.hasClass('cp-app-drive-context-collapseall')) { if (paths.length !== 1) { return; } @@ -4482,12 +4463,7 @@ define([ else if ($this.hasClass("cp-app-drive-context-newdoc")) { var ntype = $this.data('type') || 'pad'; var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; - nThen(function (waitFor) { - common.sessionStorage.put(Constants.newPadPathKey, path2, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { - common.openURL('/' + ntype + '/'); - }); + openIn(ntype, path2, APP.team); } else if ($this.hasClass("cp-app-drive-context-properties")) { if (type === 'trash') { diff --git a/www/common/notifications.js b/www/common/notifications.js index 44125ed71..caf5bfe00 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -7,8 +7,7 @@ define([ '/common/common-util.js', '/common/common-constants.js', '/customize/messages.js', - '/bower_components/nthen/index.js' -], function($, h, Hash, UI, UIElements, Util, Constants, Messages, nThen) { +], function($, h, Hash, UI, UIElements, Util, Constants, Messages) { var handlers = {}; @@ -108,21 +107,13 @@ define([ return Messages._getKey(key, [name, title, teamName]); }; content.handler = function() { - var todo = function() { - common.openURL(msg.content.href); - defaultDismiss(common, data)(); + var obj = { + p: msg.content.isTemplate ? ['template'] : undefined, + t: teamNotification || undefined, + pw: msg.content.password || '' }; - nThen(function(waitFor) { - if (msg.content.isTemplate) { - common.sessionStorage.put(Constants.newPadPathKey, ['template'], waitFor()); - } - if (teamNotification) { - common.sessionStorage.put(Constants.newPadTeamKey, teamNotification, waitFor()); - } - common.sessionStorage.put('newPadPassword', msg.content.password || '', waitFor()); - }).nThen(function() { - todo(); - }); + common.openURL(Hash.getNewPadURL(msg.content.href, obj)); + defaultDismiss(common, data)(); }; if (!content.archived) { content.dismissHandler = defaultDismiss(common, data); diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 103381f62..ce0dbcc3f 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -114,6 +114,7 @@ define([ }; var getEditor = function () { + if (!window.frames || !window.frames[0]) { return; } return window.frames[0].editor || window.frames[0].editorCell; }; @@ -416,7 +417,7 @@ define([ clearTimeout(pendingChanges[key]); delete pendingChanges[key]; }); - if (APP.stopHistory) { APP.history = false; } + if (APP.stopHistory || APP.template) { APP.history = false; } startOO(blob, type, true); }; @@ -426,14 +427,15 @@ define([ var file = getFileType(); blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type; var data = { - hash: APP.history ? ooChannel.historyLastHash : ooChannel.lastHash, - index: APP.history ? ooChannel.currentIndex : ooChannel.cpIndex + hash: (APP.history || APP.template) ? ooChannel.historyLastHash : ooChannel.lastHash, + index: (APP.history || APP.template) ? ooChannel.currentIndex : ooChannel.cpIndex }; fixSheets(); ooChannel.ready = false; ooChannel.queue = []; data.callback = function () { + if (APP.template) { APP.template = false; } resetData(blob, file); }; @@ -458,15 +460,11 @@ define([ var actions = h('div', [cancel, register, login]); var modal = UI.cornerPopup(Messages.oo_login, actions, '', {alt: true}); $(register).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); modal.delete(); }); $(login).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); modal.delete(); }); $(cancel).click(function () { @@ -1296,6 +1294,13 @@ define([ } } + if (APP.template) { + getEditor().setViewModeDisconnect(); + UI.removeLoadingScreen(); + makeCheckpoint(true); + return; + } + if (APP.history) { try { getEditor().asc_setRestriction(true); @@ -1829,6 +1834,108 @@ define([ pinImages(); }; + var loadCp = function (cp, keepQueue) { + loadLastDocument(cp, function () { + var file = getFileType(); + var type = common.getMetadataMgr().getPrivateData().ooType; + var blob = loadInitDocument(type, true); + if (!keepQueue) { ooChannel.queue = []; } + resetData(blob, file); + }, function (blob, file) { + if (!keepQueue) { ooChannel.queue = []; } + resetData(blob, file); + }); + }; + + var loadTemplate = function (href, pw, parsed) { + APP.history = true; + APP.template = true; + var editor = getEditor(); + if (editor) { editor.setViewModeDisconnect(); } + var content = parsed.content; + + // Get checkpoint + var hashes = content.hashes || {}; + var idx = sortCpIndex(hashes); + var lastIndex = idx[idx.length - 1]; + var lastCp = hashes[lastIndex] || {}; + + // Current cp or initial hash (invalid hash ==> initial hash) + var toHash = lastCp.hash || 'NONE'; + // Last hash + var fromHash = 'NONE'; + + sframeChan.query('Q_GET_HISTORY_RANGE', { + href: href, + password: pw, + channel: content.channel, + lastKnownHash: fromHash, + toHash: toHash, + }, function (err, data) { + if (err) { return void console.error(err); } + if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); } + + // The first "cp" in history is the empty doc. It doesn't include the first patch + // of the history + var initialCp = !lastCp.hash; + + var messages = (data.messages || []).slice(initialCp ? 0 : 1); + + ooChannel.queue = messages.map(function (obj) { + return { + hash: obj.serverHash, + msg: JSON.parse(obj.msg) + }; + }); + ooChannel.historyLastHash = ooChannel.lastHash; + ooChannel.currentIndex = ooChannel.cpIndex; + loadCp(lastCp, true); + }); + }; + + var openTemplatePicker = function () { + var metadataMgr = common.getMetadataMgr(); + var type = metadataMgr.getPrivateData().app; + var sframeChan = common.getSframeChannel(); + var pickerCfgInit = { + types: [type], + where: ['template'], + hidden: true + }; + var pickerCfg = { + types: [type], + where: ['template'], + }; + var onConfirm = function () { + common.openFilePicker(pickerCfg, function (data) { + if (data.type !== type) { return; } + UI.addLoadingScreen({hideTips: true}); + sframeChan.query('Q_OO_TEMPLATE_USE', { + href: data.href, + }, function (err, val) { + var parsed; + try { + parsed = JSON.parse(val); + } catch (e) { + console.error(e, val); + UI.removeLoadingScreen(); + return void UI.warn(Messages.error); + } + console.error(data); + loadTemplate(data.href, data.password, parsed); + }); + }); + }; + sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) { + if (data) { + common.openFilePicker(pickerCfgInit); + onConfirm(); + } else { + UI.alert(Messages.template_empty); + } + }); + }; + config.onInit = function (info) { var privateData = metadataMgr.getPrivateData(); @@ -1850,6 +1957,7 @@ define([ Title.setToolbar(toolbar); if (window.CP_DEV_MODE) { + var $save = common.createButton('save', true, {}, function () { makeCheckpoint(true); }); @@ -1866,18 +1974,6 @@ define([ APP.stopHistory = true; makeCheckpoint(true); }; - var loadCp = function (cp, keepQueue) { - loadLastDocument(cp, function () { - var file = getFileType(); - var type = common.getMetadataMgr().getPrivateData().ooType; - var blob = loadInitDocument(type, true); - if (!keepQueue) { ooChannel.queue = []; } - resetData(blob, file); - }, function (blob, file) { - if (!keepQueue) { ooChannel.queue = []; } - resetData(blob, file); - }); - }; var onPatch = function (patch) { // Patch on the current cp ooChannel.send(JSON.parse(patch.msg)); @@ -1971,6 +2067,10 @@ define([ load: loadSnapshot }); toolbar.$drawer.append($snapshot); + + // Import template + var $template = common.createButton('importtemplate', true, {}, openTemplatePicker); + $template.appendTo(toolbar.$drawer); })(); } @@ -2129,11 +2229,18 @@ define([ openRtChannel(function () { setMyId(); oldHashes = JSON.parse(JSON.stringify(content.hashes)); - loadDocument(newDoc, useNewDefault); initializing = false; + common.openPadChat(APP.onLocal); + + if (APP.startWithTemplate) { + var template = APP.startWithTemplate; + loadTemplate(template.href, template.password, template.content); + return; + } + + loadDocument(newDoc, useNewDefault); setEditable(!readOnly); UI.removeLoadingScreen(); - common.openPadChat(APP.onLocal); }); }; @@ -2257,8 +2364,11 @@ define([ })); SFCommon.create(waitFor(function (c) { APP.common = common = c; })); }).nThen(function (waitFor) { + common.getSframeChannel().on('EV_OO_TEMPLATE', function (data) { + APP.startWithTemplate = data; + }); common.handleNewFile(waitFor, { - noTemplates: true + //noTemplates: true }); }).nThen(function (/*waitFor*/) { andThen(common); diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index 83d887548..0560b95bd 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -55,7 +55,7 @@ define([ var addData = function (obj) { obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, ''); obj.ooVersionHash = version; - obj.ooForceVersion = localStorage.CryptPad_ooVersion || sessionStorage.CryptPad_ooVersion || ""; + obj.ooForceVersion = localStorage.CryptPad_ooVersion || ""; }; var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('Q_OO_SAVE', function (data, cb) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index bfbc08b77..62e078050 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2647,6 +2647,7 @@ define([ classic: true, }; var rt = window.rt = Listmap.create(listmapConfig); + store.driveSecret = secret; store.proxy = rt.proxy; store.loggedIn = typeof(data.userHash) !== "undefined"; @@ -2729,7 +2730,6 @@ define([ * - userHash or anonHash * Todo in cb * - LocalStore.setFSHash if needed - * - sessionStorage.User_Hash * - stuff with tokenKey * Event to outer * - requestLogin diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index 774924148..d15c2e8c6 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -82,19 +82,6 @@ define([ localStorage.setItem(Constants.userNameKey, name); if (cb) { cb(); } }; - var eraseTempSessionValues = LocalStore.eraseTempSessionValues = function () { - // delete sessionStorage values that might have been left over - // from the main page's /user redirect - [ - 'login', - 'login_user', - 'login_pass', - 'login_rmb', - 'register' - ].forEach(function (k) { - delete sessionStorage[k]; - }); - }; var logoutHandlers = []; LocalStore.logout = function (cb, isDeletion) { [ @@ -104,10 +91,8 @@ define([ 'loginToken', 'plan', ].forEach(function (k) { - sessionStorage.removeItem(k); localStorage.removeItem(k); delete localStorage[k]; - delete sessionStorage[k]; }); try { Object.keys(localStorage || {}).forEach(function (k) { @@ -122,7 +107,6 @@ define([ if (!LocalStore.getFSHash()) { LocalStore.setFSHash(Hash.createRandomHash('drive')); } - eraseTempSessionValues(); if (!isDeletion) { logoutHandlers.forEach(function (h) { diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 573879061..326b4afc7 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -2,7 +2,8 @@ define([ '/common/common-messaging.js', '/common/common-hash.js', '/common/common-util.js', -], function (Messaging, Hash, Util) { + '/bower_components/chainpad-crypto/crypto.js', +], function (Messaging, Hash, Util, Crypto) { // Random timeout between 10 and 30 times your sync time (lag + chainpad sync) var getRandomTimeout = function (ctx) { @@ -221,6 +222,11 @@ define([ toRemove = old.data; } + if (content.password) { + var key = ctx.store.driveSecret.keys.cryptKey; + content.password = Crypto.encrypt(content.password, key); + } + // Update the data channels[channel] = { mode: mode, @@ -315,6 +321,11 @@ define([ var channel = content.channel || content.teamChannel; + if (content.password) { + var key = ctx.store.driveSecret.keys.cryptKey; + content.password = Crypto.encrypt(content.password, key); + } + if (addOwners[channel]) { return void cb(true); } addOwners[channel] = { type: box.type, diff --git a/www/common/sframe-channel.js b/www/common/sframe-channel.js deleted file mode 100644 index a7edf0814..000000000 --- a/www/common/sframe-channel.js +++ /dev/null @@ -1,166 +0,0 @@ -// This file provides the API for the channel for talking to and from the sandbox iframe. -define([ - '/common/sframe-protocol.js', - '/common/common-util.js' -], function (SFrameProtocol, Util) { - - var mkTxid = function () { - return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', ''); - }; - - var create = function (ow, cb, isSandbox, sendData) { - var otherWindow; - var evReady = Util.mkEvent(true); - var handlers = {}; - var queries = {}; - - // list of handlers which are registered from the other side... - var insideHandlers = []; - var callWhenRegistered = {}; - - var chan = {}; - - // Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... }); - chan.query = function (q, content, cb, opts) { - if (!otherWindow) { throw new Error('not yet initialized'); } - if (!SFrameProtocol[q]) { - throw new Error('please only make queries are defined in sframe-protocol.js'); - } - opts = opts || {}; - var txid = mkTxid(); - var to = opts.timeout || 30000; - var timeout = setTimeout(function () { - delete queries[txid]; - console.log("Timeout making query " + q); - }, to); - queries[txid] = function (data, msg) { - clearTimeout(timeout); - delete queries[txid]; - cb(undefined, data.content, msg); - }; - evReady.reg(function () { - otherWindow.postMessage(JSON.stringify({ - txid: txid, - content: content, - q: q - }), '*'); - }); - }; - - // Fire an event. channel.event('EV_SOMETHING', { args: "whatever" }); - var event = chan.event = function (e, content) { - if (!SFrameProtocol[e]) { - throw new Error('please only fire events that are defined in sframe-protocol.js'); - } - if (e.indexOf('EV_') !== 0) { - throw new Error('please only use events (starting with EV_) for event messages'); - } - evReady.reg(function () { - otherWindow.postMessage(JSON.stringify({ content: content, q: e }), '*'); - }); - }; - - // Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... }); - // If the type is a query, your handler will be invoked with a reply function that takes - // one argument (the content to reply with). - chan.on = function (queryType, handler, quiet) { - if (!SFrameProtocol[queryType]) { - throw new Error('please only register handlers which are defined in sframe-protocol.js'); - } - (handlers[queryType] = handlers[queryType] || []).push(function (data, msg) { - handler(data.content, function (replyContent) { - if (queryType.indexOf('Q_') !== 0) { throw new Error("replies to events are invalid"); } - msg.source.postMessage(JSON.stringify({ - txid: data.txid, - content: replyContent - }), '*'); - }, msg); - }); - if (!quiet) { - event('EV_REGISTER_HANDLER', queryType); - } - }; - - // If a particular handler is registered, call the callback immediately, otherwise it will be called - // when that handler is first registered. - // channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... }); - chan.whenReg = function (queryType, cb, always) { - if (!SFrameProtocol[queryType]) { - throw new Error('please only register handlers which are defined in sframe-protocol.js'); - } - var reg = always; - if (insideHandlers.indexOf(queryType) > -1) { - cb(); - } else { - reg = true; - } - if (reg) { - (callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb); - } - }; - - // Same as whenReg except it will invoke every time there is another registration, not just once. - chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); }; - - chan.on('EV_REGISTER_HANDLER', function (content) { - if (callWhenRegistered[content]) { - callWhenRegistered[content].forEach(function (f) { f(); }); - delete callWhenRegistered[content]; - } - insideHandlers.push(content); - }); - chan.whenReg('EV_REGISTER_HANDLER', evReady.fire); - - // Make sure both iframes are ready - var isReady =false; - chan.onReady = function (h) { - if (isReady) { - return void h(); - } - if (typeof(h) !== "function") { return; } - chan.on('EV_RPC_READY', function () { isReady = true; h(); }); - }; - chan.ready = function () { - chan.whenReg('EV_RPC_READY', function () { - chan.event('EV_RPC_READY'); - }); - }; - - var txid; - window.addEventListener('message', function (msg) { - var data = JSON.parse(msg.data); - if (ow !== msg.source) { - return; - //console.log("DROP Message from unexpected source"); - //console.log(msg); - } else if (!otherWindow) { - otherWindow = ow; - sendData = sendData || {}; - sendData.txid = data.txid; - ow.postMessage(JSON.stringify(sendData), '*'); - cb(chan); - } else if (typeof(data.q) === 'string' && handlers[data.q]) { - handlers[data.q].forEach(function (f) { - f(data || JSON.parse(msg.data), msg); - data = undefined; - }); - } else if (typeof(data.q) === 'undefined' && queries[data.txid]) { - queries[data.txid](data, msg); - } else if (data.txid === txid) { - // stray message from init - return; - } else { - console.log("DROP Unhandled message"); - console.log(msg); - } - }); - if (isSandbox) { - // we're in the sandbox - otherWindow = ow; - evReady.fire(); - cb(chan); - } - }; - - return { create: create }; -}); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 086841f73..f1fd0ac8d 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -26,7 +26,7 @@ define([ }; var AppConfig; var Test; - var password; + var password, newPadPassword; var initialPathInDrive; var burnAfterReading; @@ -123,6 +123,9 @@ define([ }); SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) { Utils.sframeChan = sframeChan = sfc; + window.CryptPad_loadingError = function (e) { + sfc.event('EV_LOADING_ERROR', e) + }; })); }); window.addEventListener('message', whenReady); @@ -131,12 +134,28 @@ define([ if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); } }); + try { + var parsed = Utils.Hash.parsePadUrl(currentPad.href); + var options = parsed.getOptions(); + if (options.loginOpts) { + var loginOpts = Utils.Hash.decodeDataOptions(options.loginOpts); + if (loginOpts.createReadme) { Cryptpad.createReadme = true; } + if (loginOpts.mergeAnonDrive) { Cryptpad.migrateAnonDrive = true; } + // Remove newPadOpts from the hash + delete options.loginOpts; + currentPad.href = parsed.getUrl(options); + currentPad.hash = parsed.hashData.getHash ? parsed.hashData.getHash(options) + : ''; + } + } catch (e) { console.error(e); } + Cryptpad.ready(waitFor(), { driveEvents: cfg.driveEvents, currentPad: currentPad }); - if (window.history && window.history.replaceState && currentPad.hash) { + // Remove the login hash if needed + if (window.history && window.history.replaceState && (currentPad.hash || window.location.hash)) { var nHash = currentPad.hash; if (!/^#/.test(nHash)) { nHash = '#' + nHash; } window.history.replaceState({}, window.document.title, nHash); @@ -196,15 +215,53 @@ define([ } // Rendered (maybe hidden) hash var renderedParsed = Utils.Hash.parsePadUrl(window.location.href); - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = renderedParsed.getUrl(opts); - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHref(renderedParsed.getUrl(opts)); } })); }; + // New pad options + var options = parsed.getOptions(); + if (options.newPadOpts) { + try { + var newPad = Utils.Hash.decodeDataOptions(options.newPadOpts); + Cryptpad.initialTeam = newPad.t; + Cryptpad.initialPath = newPad.p; + if (newPad.pw) { + try { + var uHash = Utils.LocalStore.getUserHash(); + var uSecret = Utils.Hash.getSecrets('drive', uHash); + var uKey = uSecret.keys.cryptKey; + newPadPassword = Crypto.decrypt(newPad.pw, uKey); + } catch (e) { console.error(e); } + } + if (newPad.d) { + Cryptpad.fromFileData = newPad.d; + var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href); + if (_parsed1.hashData.type === 'pad' && _parsed1.type !== parsed.type) { + delete Cryptpad.fromFileData; + } + } + } catch (e) { + console.error(e, parsed.hashData.newPadOpts); + } + delete options.newPadOpts; + + currentPad.href = parsed.getUrl(options); + currentPad.hash = parsed.hashData.getHash ? parsed.hashData.getHash(options) + : ''; + var version = parsed.hashData.version; + parsed = Utils.Hash.parsePadUrl(currentPad.href); + Cryptpad.setTabHash(currentPad.hash); + + // If it's a new pad, don't check password + if (version === 4) { + return void todo(); + } + // Otherwise, continue + } + + if (!parsed.hashData) { // No hash, no need to check for a password return void todo(); } @@ -335,9 +392,8 @@ define([ password = val; }), parsed.getUrl()); }).nThen(function (w) { - if (!password && !stored && sessionStorage.newPadPassword) { - passwordCfg.value = sessionStorage.newPadPassword; - delete sessionStorage.newPadPassword; + if (!password && !stored && newPadPassword) { + passwordCfg.value = newPadPassword; } if (parsed.type === "file") { @@ -357,7 +413,7 @@ define([ waitFor.abort(); return; } - if (!isNew) { return void todo(); } + if (!e && !isNew) { return void todo(); } if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) { // Error, wrong password stored, the view seed has changed with the password // password will never work @@ -422,9 +478,6 @@ define([ var defaultTitle = Utils.UserObject.getDefaultName(parsed); var edPublic, curvePublic, notifications, isTemplate; var settings = {}; - var forceCreationScreen = cfg.useCreationScreen && - sessionStorage[Utils.Constants.displayPadCreationScreen]; - delete sessionStorage[Utils.Constants.displayPadCreationScreen]; var isSafe = ['debug', 'profile', 'drive', 'teams'].indexOf(currentPad.app) !== -1; var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); @@ -457,6 +510,8 @@ define([ fileHost: ApiConfig.fileHost, readOnly: readOnly, isTemplate: isTemplate, + newTemplate: Array.isArray(Cryptpad.initialPath) + && Cryptpad.initialPath[0] === "template", feedbackAllowed: Utils.Feedback.state, isPresent: parsed.hashData && parsed.hashData.present, isEmbed: parsed.hashData && parsed.hashData.embed, @@ -467,7 +522,6 @@ define([ }, isNewFile: isNewFile, isDeleted: isNewFile && currentPad.hash.length > 0, - forceCreationScreen: forceCreationScreen, password: password, channel: secret.channel, enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default @@ -508,18 +562,10 @@ define([ sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); Utils.LocalStore.onLogin(function () { - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = currentPad.hash; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHash(currentPad.hash); }); Utils.LocalStore.onLogout(function () { - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = currentPad.hash; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHash(currentPad.hash); sframeChan.event('EV_LOGOUT'); }); @@ -603,9 +649,10 @@ define([ Cryptpad.mailbox.execCommand(data, cb); }); - sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) { - sessionStorage.redirectTo = currentPad.href; - cb(); + sframeChan.on('EV_SET_LOGIN_REDIRECT', function (page) { + var href = Utils.Hash.hashToHref('', page); + var url = Utils.Hash.getNewPadURL(href, { href: currentPad.href }); + window.location.href = url; }); sframeChan.on('Q_STORE_IN_TEAM', function (data, cb) { @@ -1033,11 +1080,10 @@ define([ password: password, title: currentTitle }; - sessionStorage[Utils.Constants.newPadFileData] = JSON.stringify(data); - window.open(window.location.pathname); - setTimeout(function () { - delete sessionStorage[Utils.Constants.newPadFileData]; - }, 100); + var obj = { d: data }; + var href = window.location.pathname; + var url = Utils.Hash.getNewPadURL(href, obj); + window.open(url); }); // Messaging @@ -1097,6 +1143,10 @@ define([ nSecret = Utils.Hash.getSecrets('drive', hash, password); } } + if (data.href) { + var _parsed = Utils.Hash.parsePadUrl(data.href); + nSecret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, data.password); + } var channel = nSecret.channel; var validate = nSecret.keys.validateKey; var crypto = Crypto.createEncryptor(nSecret.keys); @@ -1131,15 +1181,6 @@ define([ }); }); - sframeChan.on('Q_SESSIONSTORAGE_PUT', function (data, cb) { - if (typeof (data.value) === "undefined") { - delete sessionStorage[data.key]; - } else { - sessionStorage[data.key] = data.value; - } - cb(); - }); - sframeChan.on('Q_IS_ONLY_IN_SHARED_FOLDER', function (data, cb) { Cryptpad.isOnlyInSharedFolder(secret.channel, function (err, t) { if (err) { return void cb({error: err}); } @@ -1163,11 +1204,7 @@ define([ var hiddenParsed = Utils.Hash.parsePadUrl(window.location.href); // Update the hash in the address bar - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = hiddenParsed.getUrl(opts); - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHref(hiddenParsed.getUrl(opts)); }); @@ -1277,6 +1314,10 @@ define([ sframeChan.on('Q_TEMPLATE_USE', function (data, cb) { Cryptpad.useTemplate(data, Cryptget, cb); }); + sframeChan.on('Q_OO_TEMPLATE_USE', function (data, cb) { + data.oo = true; + Cryptpad.useTemplate(data, Cryptget, cb); + }); sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) { Cryptpad.listTemplates(type, function (err, templates) { cb(templates.length > 0); @@ -1514,11 +1555,7 @@ define([ // in the address bar Cryptpad.padRpc.onChannelDeleted.reg(function (channel) { if (channel !== secret.channel) { return; } - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = currentPad.href; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHref(currentPad.href); }); // Join the netflux channel @@ -1602,13 +1639,9 @@ define([ Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); // Update the hash in the address bar - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = newHash; currentPad.hash = newHash; currentPad.href = '/' + parsed.type + '/#' + newHash; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHash(newHash); // Update metadata values and send new metadata inside parsed = Utils.Hash.parsePadUrl(currentPad.href); @@ -1639,19 +1672,44 @@ define([ rtConfig.metadata.validateKey = (secret.keys && secret.keys.validateKey) || undefined; Utils.rtConfig = rtConfig; + var templatePw; nThen(function(waitFor) { if (data.templateId) { if (data.templateId === -1) { + isTemplate = true; initialPathInDrive = ['template']; return; } Cryptpad.getPadData(data.templateId, waitFor(function (err, d) { data.template = d.href; + templatePw = d.password; })); } }).nThen(function () { var cryptputCfg = $.extend(true, {}, rtConfig, {password: password}); if (data.template) { + // Start OO with a template... + // Cryptget and give href, password and content to inner + if (parsed.type === "sheet") { + var then = function () { + startRealtime(rtConfig); + cb(); + }; + var _parsed = Utils.Hash.parsePadUrl(data.template); + Cryptget.get(_parsed.hash, function (err, val) { + if (err || !val) { return void then(); } + try { + var parsed = JSON.parse(val); + sframeChan.event('EV_OO_TEMPLATE', { + href: data.template, + password: templatePw, + content: parsed + }); + } catch (e) { console.error(e); } + then(); + }, {password: templatePw}); + return; + } // Pass rtConfig to useTemplate because Cryptput will create the file and // we need to have the owners and expiration time in the first line on the // server diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index f7a2c41c3..3eb86ff93 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -307,9 +307,8 @@ define([ ctx.sframeChan.event('EV_SET_HASH', hash); }; - funcs.setLoginRedirect = function (cb) { - cb = cb || $.noop; - ctx.sframeChan.query('Q_SET_LOGIN_REDIRECT', null, cb); + funcs.setLoginRedirect = function (page) { + ctx.sframeChan.query('EV_SET_LOGIN_REDIRECT', page); }; funcs.isPresentUrl = function (cb) { @@ -463,15 +462,6 @@ define([ }); }; - funcs.sessionStorage = { - put: function (key, value, cb) { - ctx.sframeChan.query('Q_SESSIONSTORAGE_PUT', { - key: key, - value: value - }, cb); - } - }; - funcs.setDisplayName = function (name, cb) { cb = cb || $.noop; ctx.sframeChan.query('Q_SETTINGS_SET_DISPLAY_NAME', name, cb); @@ -758,9 +748,7 @@ define([ var mustLogin = privateData.registeredOnly; if (mustLogin) { UI.alert(Messages.mustLogin, function () { - funcs.setLoginRedirect(function () { - funcs.gotoURL('/login/'); - }); + funcs.setLoginRedirect('login'); }, {forefront: true}); return; } diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js deleted file mode 100644 index 5eb9d829e..000000000 --- a/www/common/sframe-protocol.js +++ /dev/null @@ -1,271 +0,0 @@ -// This file defines all of the RPC calls which are used between the inner and outer iframe. -// Define *querys* (which expect a response) using Q_ -// Define *events* (which expect no response) using EV_ -// Please document the queries and events you create, and please please avoid making generic -// "do stuff" events/queries which are used for many different things because it makes the -// protocol unclear. -// -// WARNING: At this point, this protocol is still EXPERIMENTAL. This is not it's final form. -// We need to define protocol one piece at a time and then when we are satisfied that we -// fully understand the problem, we will define the *right* protocol and this file will be dynomited. -// -define({ - // When the iframe first launches, this query is sent repeatedly by the controller - // to wait for it to awake and give it the requirejs config to use. - 'Q_INIT': true, - - // When either the outside or inside registers a query handler, this is sent. - 'EV_REGISTER_HANDLER': true, - - // When an iframe is ready to receive messages - 'EV_RPC_READY': true, - - // Realtime events called from the outside. - // When someone joins the pad, argument is a string with their netflux id. - 'EV_RT_JOIN': true, - // When someone leaves the pad, argument is a string with their netflux id. - 'EV_RT_LEAVE': true, - // When you have been disconnected, no arguments. - 'EV_RT_DISCONNECT': true, - // When you have connected, argument is an object with myID: string, members: list, readOnly: boolean. - 'EV_RT_CONNECT': true, - // Called after the history is finished synchronizing, no arguments. - 'EV_RT_READY': true, - // Called when the server returns an error in a pad (EEXPIRED, EDELETED). - 'EV_RT_ERROR': true, - // Called from both outside and inside, argument is a (string) chainpad message. - 'Q_RT_MESSAGE': true, - - // Called from the outside, this informs the inside whenever the user's data has been changed. - // The argument is the object representing the content of the user profile minus the netfluxID - // which changes per-reconnect. - 'EV_METADATA_UPDATE': true, - - // Takes one argument only, the title to set for the CURRENT pad which the user is looking at. - // This changes the pad title in drive ONLY, the pad title needs to be changed inside of the - // iframe and synchronized with the other users. This will not trigger a EV_METADATA_UPDATE - // because the metadata contained in EV_METADATA_UPDATE does not contain the pad title. - // It also sets the page (tab) title to the selected title, unles it is overridden by - // the EV_SET_TAB_TITLE event. - 'Q_SET_PAD_TITLE_IN_DRIVE': true, - // Set the page title (tab title) to the selected value which will override the pad title. - // The new title value can contain {title}, which will be replaced by the pad title when it - // is set or modified. - 'EV_SET_TAB_TITLE': true, - - // Update the user's display-name which will be shown to contacts and people in the same pads. - 'Q_SETTINGS_SET_DISPLAY_NAME': true, - - // Log the user out in all the tabs - 'Q_LOGOUT': true, - // Tell the user that he has been logged out from outside (probably from another tab) - 'EV_LOGOUT': true, - - // When moving to the login or register page from a pad, we need to redirect to that pad at the - // end of the login process. This query set the current href to the sessionStorage. - 'Q_SET_LOGIN_REDIRECT': true, - - // Store the editing or readonly link of the current pad to the clipboard (share button). - 'Q_STORE_LINK_TO_CLIPBOARD': true, - - // Use anonymous rpc from inside the iframe (for avatars & pin usage). - 'Q_ANON_RPC_MESSAGE': true, - - // Get the user's pin limit, usage and plan - 'Q_PIN_GET_USAGE': true, - - // Write/update the login block when the account password is changed - 'Q_WRITE_LOGIN_BLOCK': true, - - // Remove login blocks - 'Q_REMOVE_LOGIN_BLOCK': true, - - // Check the pin limit to determine if we can store the pad in the drive or if we should. - // display a warning - 'Q_GET_PIN_LIMIT_STATUS': true, - - // Move a pad to the trash when using the forget button. - 'Q_MOVE_TO_TRASH': true, - - // 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, - - // Save a pad as a template using the toolbar button - 'Q_SAVE_AS_TEMPLATE': true, - - // Friend requests from the userlist - 'Q_SEND_FRIEND_REQUEST': true, // Up query - 'Q_INCOMING_FRIEND_REQUEST': true, // Down query - 'EV_FRIEND_REQUEST': true, // Down event when the request is complete - - // Set the tab notification when the content of the pad changes - 'EV_NOTIFY': true, - - // Send the new settings to the inner iframe when they are changed in the proxy - 'EV_SETTINGS_UPDATE': true, - - // Get and set (pad) attributes stored in the drive from the inner iframe - 'Q_GET_ATTRIBUTE': true, - 'Q_SET_ATTRIBUTE': true, - 'Q_GET_PAD_ATTRIBUTE': true, - 'Q_SET_PAD_ATTRIBUTE': true, - - // Check if a pad is only in a shared folder or (also) in the main drive. - // This allows us to change the behavior of some buttons (trash icon...) - 'Q_IS_ONLY_IN_SHARED_FOLDER': true, - - // Open/close the File picker (sent from the iframe to the outside) - 'EV_FILE_PICKER_OPEN': true, - 'EV_FILE_PICKER_CLOSE': true, - 'EV_FILE_PICKER_REFRESH': true, - // File selected in the file picker: sent from the filepicker iframe to the outside - // and then send to the inner iframe - 'EV_FILE_PICKED': true, - - // Get all the files from the drive to display them in a file picker secure app - 'Q_GET_FILES_LIST': true, - - // Template picked, replace the content of the pad - 'Q_TEMPLATE_USE': true, - // Check if we have template(s) for the selected pad type - 'Q_TEMPLATE_EXIST': true, - - // File upload queries and events - 'Q_UPLOAD_FILE': true, - 'EV_FILE_UPLOAD_STATE': true, - 'Q_CANCEL_PENDING_FILE_UPLOAD': true, - - // Make the browser window navigate to a given URL, if no URL is passed then it will reload. - 'EV_GOTO_URL': true, - // Make the parent window open a given URL in a new tab. It allows us to keep sessionStorage - // form the parent window. - 'EV_OPEN_URL': true, - - // Present mode URL - 'Q_PRESENT_URL_GET_VALUE': true, - 'EV_PRESENT_URL_SET_VALUE': true, - - // Put one or more entries to the cache which will go in localStorage. - // Cache is wiped after each new release - 'EV_CACHE_PUT': true, - - // Chat - 'EV_CHAT_EVENT': true, - 'Q_CHAT_COMMAND': true, - 'Q_CHAT_OPENPADCHAT': true, - - // Cursor - 'EV_CURSOR_EVENT': true, - 'Q_CURSOR_COMMAND': true, - 'Q_CURSOR_OPENCHANNEL': true, - - // Put one or more entries to the localStore which will go in localStorage. - 'EV_LOCALSTORE_PUT': true, - // Put one entry in the parent sessionStorage - 'Q_SESSIONSTORAGE_PUT': true, - - // Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads - // in the drive at registration. - 'Q_MERGE_ANON_DRIVE': true, - - // Add or remove the avatar from the profile. - // We have to pin/unpin the avatar and store/remove the value from the user object - 'Q_PROFILE_AVATAR_ADD': true, - 'Q_PROFILE_AVATAR_REMOVE': true, - - // Store outside and get thumbnails inside (stored with localForage (indexedDB) outside) - 'Q_THUMBNAIL_SET': true, - 'Q_THUMBNAIL_GET': true, - - // Settings app only - // Clear all thumbnails - 'Q_THUMBNAIL_CLEAR': true, - // Backup and restore a drive - 'Q_SETTINGS_DRIVE_GET': true, - 'Q_SETTINGS_DRIVE_SET': true, - 'Q_SETTINGS_DRIVE_RESET': true, - // Logout from all the devices where the account is logged in - 'Q_SETTINGS_LOGOUT': true, - // Import pads from this computer's anon session into the current user account - 'Q_SETTINGS_IMPORT_LOCAL': true, - 'Q_SETTINGS_DELETE_ACCOUNT': true, - - // Store the language selected in the iframe into localStorage outside - 'Q_LANGUAGE_SET': true, - - // Anonymous users can empty their drive and remove FS_hash from localStorage - 'EV_BURN_ANON_DRIVE': true, - // Inner drive needs to send command and receive updates from the async store - 'Q_DRIVE_USEROBJECT': true, - 'Q_DRIVE_GETOBJECT': true, - 'Q_DRIVE_RESTORE': true, - // Get the pads deleted from the server by other users to remove them from the drive - 'Q_DRIVE_GETDELETED': true, - // Store's userObject need to send log messages to inner to display them in the UI - 'EV_DRIVE_LOG': true, - // Refresh the drive when the drive has changed ('change' or 'remove' events) - 'EV_DRIVE_CHANGE': true, - 'EV_DRIVE_REMOVE': true, - // Set shared folder hash in the address bar - 'EV_DRIVE_SET_HASH': true, - - // Remove an owned pad from the server - 'Q_REMOVE_OWNED_CHANNEL': true, - // Clear an owned pad from the server (preserve metadata) - 'Q_CLEAR_OWNED_CHANNEL': true, - - // Notifications about connection and disconnection from the network - 'EV_NETWORK_DISCONNECT': true, - 'EV_NETWORK_RECONNECT': true, - // Reload on new version - 'EV_NEW_VERSION': true, - - // Pad creation screen: create a pad with the selected attributes (owned, expire) - 'Q_CREATE_PAD': true, - // Get the available templates - 'Q_CREATE_TEMPLATES': true, - - // This is for sending data out of the iframe when we are in testing mode - // The exact protocol is defined in common/test.js - 'EV_TESTDATA': true, - - // OnlyOffice: save a new version - 'Q_OO_SAVE': true, - - // Ask for the pad password when a pad is protected - 'EV_PAD_PASSWORD': true, - 'Q_PAD_PASSWORD_VALUE': true, - // Change pad password - 'Q_PAD_PASSWORD_CHANGE': true, - - // Migrate drive to owned drive - 'Q_CHANGE_USER_PASSWORD': true, - - // Loading events to display in the loading screen - 'EV_LOADING_INFO': true, - // Critical error outside the iframe during loading screen - 'EV_LOADING_ERROR': true, - - // Chrome 68 bug... - 'EV_CHROME_68': true, - - // Get all existing tags - 'Q_GET_ALL_TAGS': true, - - // Store pads in the drive - 'EV_AUTOSTORE_DISPLAY_POPUP': true, - 'Q_AUTOSTORE_STORE': true, - 'Q_IS_PAD_STORED': true, - - // Import mediatag from a pad - 'Q_IMPORT_MEDIATAG': true, - - // Ability to get a pad's content from its hash - 'Q_CRYPTGET': true, - 'EV_CRYPTGET_DISCONNECT': true, - -}); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index b3187571a..90cd0ff08 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -773,15 +773,11 @@ MessengerUI, Messages) { ]); $msg.find('a.cp-pnp-login').click(function (ev) { ev.preventDefault(); - Common.setLoginRedirect(function () { - window.parent.location = o + '/login/'; - }); + Common.setLoginRedirect('login'); }); $msg.find('a.cp-pnp-register').click(function (ev) { ev.preventDefault(); - Common.setLoginRedirect(function () { - window.parent.location = o + '/register/'; - }); + Common.setLoginRedirect('register'); }); $('.cp-toolbar-top').append($msg); //UI.addTooltips(); diff --git a/www/common/translations/messages.fi.json b/www/common/translations/messages.fi.json index 3b5835ff7..fe4ee1f64 100644 --- a/www/common/translations/messages.fi.json +++ b/www/common/translations/messages.fi.json @@ -188,12 +188,12 @@ "help_button": "Ohje", "historyText": "Historia", "historyButton": "Näytä asiakirjan historia", - "history_next": "Uudempi versio", - "history_prev": "Vanhempi versio", + "history_next": "Seuraava versio", + "history_prev": "Edellinen versio", "history_loadMore": "Lataa lisää historiatietoja", "history_closeTitle": "Sulje historia", "history_restoreTitle": "Palauta asiakirjan valittu versio", - "history_restorePrompt": "Oletko varma, että haluat korvata asiakirjan nykyisen version esitetyllä versiolla?", + "history_restorePrompt": "Oletko varma, että haluat korvata asiakirjan tämänhetkisen version näytetyllä versiolla?", "history_restoreDone": "Asiakirja palautettu", "history_version": "Versio:", "openLinkInNewTab": "Avaa linkki uuteen välilehteen", @@ -452,7 +452,7 @@ "settings_backupHint": "Varmuuskopioi tai palauta CryptDrivesi sisältö kokonaisuudessaan. Varmuuskopio ei sisällä padiesi sisältöä, ainoastaan niiden käyttöön tarvittavat avaimet.", "settings_backup": "Varmuuskopioi", "settings_restore": "Palauta", - "settings_backupHint2": "Lataa kaikkien padiesi nykyinen sisältö. Padit ladataan luettavassa tiedostomuodossa, jos sellainen on saatavilla.", + "settings_backupHint2": "Lataa kaikkien asiakirjojen nykyinen sisältö tietokoneellesi. Asiakirjat ladataan muissa sovelluksissa toimivissa tiedostomuodoissa, jos se on mahdollista. Jos sopivaa tiedostomuotoa ei ole saatavilla, asiakirjat ladataan CryptPad-yhteensopivassa tiedostomuodossa.", "settings_backup2": "Lataa oma CryptDrive tietokoneellesi", "settings_backup2Confirm": "Tämä lataa kaikki CryptDrivesi padit ja tiedostot tietokoneellesi. Jos haluat jatkaa, valitse nimi ja paina OK", "settings_exportTitle": "Vie oma CryptDrive", @@ -1176,9 +1176,9 @@ "settings_safeLinksTitle": "Turvalliset linkit", "settings_cat_security": "Luottamuksellisuus", "imprint": "Oikeudellinen huomautus", - "oo_sheetMigration_anonymousEditor": "Taulukon muokkaaminen on poistettu käytöstä anonyymeille käyttäjille, kunnes rekisteröitynyt käyttäjä päivittää sen viimeisimpään versioon.", + "oo_sheetMigration_anonymousEditor": "Rekisteröitymättömät käyttäjät eivät voi muokata taulukkoa ennen kuin rekisteröitynyt käyttäjä päivittää sen viimeisimpään versioon.", "oo_sheetMigration_complete": "Päivitetty versio saatavilla, paina OK ladataksesi uudelleen.", - "oo_sheetMigration_loading": "Päivitetään taulukkoasi viimeisimpään versioon", + "oo_sheetMigration_loading": "Päivitetään taulukkoasi viimeisimpään versioon. Ole hyvä ja odota noin 1 minuutti.", "oo_exportInProgress": "Vienti menossa", "oo_importInProgress": "Tuonti menossa", "oo_invalidFormat": "Tätä tiedostoa ei voida tuoda", @@ -1404,5 +1404,60 @@ "readme_cat1_l2": "Avaa padeja CryptDrivestasi: kaksoisnapsauta padin kuvaketta avataksesi sen.", "readme_cat1_l1": "Luo padi: Siirry CryptDriveesi, napsauta {0} ja sitten {1}, ja voit luoda padin.", "readme_cat1": "Tutustu CryptDriveesi", - "readme_p2": "Tämä padi toimii pikaisena perehdytyksenä CryptPadin ominaisuuksiin - kuinka tehdä muistiinpanoja, järjestellä niitä ja tehdä yhteistyötä niiden parissa." + "readme_p2": "Tämä padi toimii pikaisena perehdytyksenä CryptPadin ominaisuuksiin - kuinka tehdä muistiinpanoja, järjestellä niitä ja tehdä yhteistyötä niiden parissa.", + "fm_shareFolderPassword": "Suojaa kansio salasanalla (vapaaehtoinen)", + "access_destroyPad": "Tuhoa tämä asiakirja tai kansio lopullisesti", + "fm_deletedFolder": "Poistettu kansio", + "admin_limitUser": "Käyttäjän julkinen avain", + "team_exportButton": "Lataa", + "team_exportHint": "Lataa kaikki tiimin CryptDriveen tallennetut asiakirjat tietokoneellesi. Asiakirjat ladataan muissa sovelluksissa toimivissa tiedostomuodoissa, jos se on mahdollista. Jos sopivaa tiedostomuotoa ei ole saatavilla, asiakirjat ladataan CryptPad-yhteensopivassa tiedostomuodossa.", + "team_exportTitle": "Lataa tiimin CryptDrive", + "admin_cat_quota": "Käyttäjätallennustila", + "admin_invalLimit": "Virheellinen arvo kiintiökentässä", + "admin_invalKey": "Virheellinen julkinen avain", + "admin_limitSetNote": "Huomautus", + "admin_limitMB": "Kiintiö (megatavuissa)", + "admin_setlimitTitle": "Ota mukautettu kiintiö käyttöön", + "admin_setlimitHint": "Aseta mukautettu tallennustilakiintiö käyttäjälle tämän julkisen avaimen avulla. Voit päivittää tai poistaa olemassaolevan tallennustilakiintiön.", + "admin_limitNote": "Huomautus: {0}", + "admin_limitPlan": "Tilaus: {0}", + "admin_defaultlimitHint": "Tallennustilakiintiö käyttäjien ja tiimien CryptDriveille, joilla ei ole erikseen määriteltyjä sääntöjä", + "admin_defaultlimitTitle": "Tallennustilakiintiö (Mt)", + "admin_getlimitsHint": "Listaa muokatut tallennustilakiintiöt, jotka ovat käytössä CryptPad-palvelimellasi.", + "admin_getlimitsTitle": "Muokatut tallennustilakiintiöt", + "admin_limit": "Nykyinen rajoitus: {0}", + "admin_setlimitButton": "Aseta rajoitus", + "admin_registrationAllow": "Avaa", + "admin_registrationButton": "Sulje", + "admin_registrationTitle": "Sulje rekisteröityminen", + "admin_registrationHint": "Älä salli uusien käyttäjien rekisteröitymistä", + "snapshots_cantMake": "Tilannevedoksen luominen epäonnistui. Yhteytesi on katkennut.", + "snapshots_notFound": "Tämä tilannevedos ei ole enää saatavilla, koska asiakirjan historia on poistettu.", + "snapshot_error_exists": "Tästä versiosta on jo olemassa tilannevedos", + "snapshots_ooPickVersion": "Valitse versio, jotta voit luoda tilannevedoksen", + "oo_version": "Versio: ", + "oo_version_latest": "Viimeisin", + "snapshots_delete": "Poista", + "oo_deletedVersion": "Tämä versio ei ole enää saatavilla historiassa.", + "snapshots_close": "Sulje", + "snapshots_restore": "Palauta", + "snapshots_open": "Avaa", + "snapshots_placeholder": "Tilannevedoksen otsikko", + "snapshots_new": "Uusi tilannevedos", + "snapshots_button": "Tilannevedokset", + "snaphot_title": "Tilannevedos", + "infobar_versionHash": "Katselet asiakirjan vanhaa versiota ({0}).", + "history_restoreDriveDone": "CryptDrive palautettu", + "history_restoreDrivePrompt": "Oletko varma, että haluat korvata CryptDriven nykyisen version valitulla versiolla?", + "history_restoreDriveTitle": "Palauta CryptDrive valitsemaasi versioon", + "history_userNext": "Seuraava laatija", + "history_fastNext": "Seuraava muokkaussessio", + "history_userPrev": "Edellinen laatija", + "history_fastPrev": "Edellinen muokkaussessio", + "share_versionHash": "Olet jakamassa valitsemasi historiaversion asiakirjastasi vain luku-tilassa. Tämä antaa myös katseluoikeuden asiakirjan kaikkiin versioihin.", + "history_shareTitle": "Jaa linkki tähän versioon", + "history_cantRestore": "Palauttaminen epäonnistui. Yhteytesi on katkennut.", + "history_close": "Sulje", + "history_restore": "Palauta", + "share_bar": "Luo linkki" } diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index ce19c96ad..7d8a84efb 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1459,5 +1459,11 @@ "admin_limitUser": "Clé publique de l'utilisateur", "fm_shareFolderPassword": "Protéger ce dossier avec un mot de passe (optionnel)", "access_destroyPad": "Détruire ce document ou dossier définitivement", - "fm_deletedFolder": "Dossier supprimé" + "fm_deletedFolder": "Dossier supprimé", + "loading_state_5": "Reconstruction du document", + "loading_state_4": "Chargement des équipes", + "loading_state_3": "Chargement des dossiers partagés", + "loading_state_2": "Mise à jour du contenu", + "loading_state_1": "Chargement du drive", + "loading_state_0": "Construction de l'interface" } diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 010287444..fa96fc5a7 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1459,5 +1459,13 @@ "admin_limitUser": "User's public key", "fm_deletedFolder": "Deleted folder", "access_destroyPad": "Destroy this document or folder permanently", - "fm_shareFolderPassword": "Protect this folder with a password (optional)" + "fm_shareFolderPassword": "Protect this folder with a password (optional)", + "loading_state_0": "Build interface", + "loading_state_1": "Load drive", + "loading_state_2": "Update content", + "loading_state_3": "Load shared folders", + "loading_state_4": "Load Teams", + "loading_state_5": "Reconstruct document", + "tag_add": "Add", + "tag_edit": "Edit" } diff --git a/www/debug/main.js b/www/debug/main.js index 68055b1d4..b901beec0 100644 --- a/www/debug/main.js +++ b/www/debug/main.js @@ -53,14 +53,9 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var hash = localStorage[Constants.userHashKey]; + var hash = localStorage[Constants.userHashKey] || localStorage[Constants.fileHashKey]; var drive = hash && ('#'+hash === window.location.hash); if (!window.location.hash) { - if (!hash) { - sessionStorage.redirectTo = '/debug/'; - window.location.href = '/login/'; - return; - } drive = true; window.location.hash = hash; } else { diff --git a/www/drive/main.js b/www/drive/main.js index 8db013c0f..5eb9dbc67 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -46,9 +46,12 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { var afterSecrets = function (Cryptpad, Utils, secret, cb, sframeChan) { - var _hash = hash.slice(1); - if (_hash && Utils.LocalStore.isLoggedIn()) { - // Add a shared folder! + var parsed = Utils.Hash.parsePadUrl(href); + var isSf = parsed.hashData && parsed.hashData.type === 'pad'; + if (!isSf) { return void cb(); } + + // SF and logged in: add shared folder + if (Utils.LocalStore.isLoggedIn()) { Cryptpad.addSharedFolder(null, secret, function (id) { if (id && typeof(id) === "object" && id.error) { sframeChan.event("EV_RESTRICTED_ERROR"); @@ -65,16 +68,16 @@ define([ cb(); }); return; - } else if (_hash) { - var id = Utils.Util.createRandomInteger(); - window.CryptPad_newSharedFolder = id; - var data = { - href: Utils.Hash.getRelativeHref(Cryptpad.currentPad.href), - password: secret.password - }; - return void Cryptpad.loadSharedFolder(id, data, cb); } - cb(); + + // Anon shared folder + var id = Utils.Util.createRandomInteger(); + window.CryptPad_newSharedFolder = id; + var data = { + href: Utils.Hash.getRelativeHref(Cryptpad.currentPad.href), + password: secret.password + }; + Cryptpad.loadSharedFolder(id, data, cb); }; var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('EV_BURN_ANON_DRIVE', function () { diff --git a/www/file/inner.js b/www/file/inner.js index 81019d0be..b2aef53ed 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -231,9 +231,7 @@ define([ if (!common.isLoggedIn()) { UI.removeLoadingScreen(); return UI.alert(Messages.upload_mustLogin, function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); }); } diff --git a/www/login/main.js b/www/login/main.js index 40a82b27e..768c012a8 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -67,10 +67,8 @@ define([ }); }); $('#register').on('click', function () { - if (sessionStorage) { - if ($uname.val()) { - sessionStorage.login_user = $uname.val(); - } + if ($uname.val()) { + localStorage.login_user = $uname.val(); } window.location.href = '/register/'; }); diff --git a/www/logout/main.js b/www/logout/main.js index acd8e8b02..d23bb8969 100644 --- a/www/logout/main.js +++ b/www/logout/main.js @@ -1,5 +1,4 @@ define(['/bower_components/localforage/dist/localforage.min.js'], function (localForage) { localForage.clear(); - sessionStorage.clear(); localStorage.clear(); }); diff --git a/www/pad/inner.html b/www/pad/inner.html index b7ccd4d00..e4dbcdf95 100644 --- a/www/pad/inner.html +++ b/www/pad/inner.html @@ -1,5 +1,5 @@ - + diff --git a/www/pad/inner.js b/www/pad/inner.js index 19c2063d8..eeabbb337 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -47,6 +47,7 @@ define([ '/bower_components/diff-dom/diffDOM.js', + 'css!/customize/src/print.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/pad/app-pad.less' @@ -500,6 +501,12 @@ define([ var mkPrintButton = function (framework, editor) { var $printButton = framework._.sfCommon.createButton('print', true); $printButton.click(function () { + /* + // NOTE: alternative print system in case we keep having more issues on Firefox + var $iframe = $('html').find('iframe'); + var iframe = $iframe[0].contentWindow; + iframe.print(); + */ editor.execCommand('print'); framework.feedback('PRINT_PAD'); }); diff --git a/www/profile/inner.js b/www/profile/inner.js index cddae4611..7a872fdd0 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -611,15 +611,11 @@ define([ var actions = h('div', [cancel, register, login]); var modal = UI.cornerPopup(Messages.profile_login, actions, '', {alt: true}); $(register).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); modal.delete(); }); $(login).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); modal.delete(); }); $(cancel).click(function () { diff --git a/www/register/main.js b/www/register/main.js index c6e9cd546..58385b6c8 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -37,9 +37,9 @@ define([ var $passwd = $('#password'); var $confirm = $('#password-confirm'); - if (sessionStorage.login_user) { - delete sessionStorage.login_user; - $uname.val(sessionStorage.login_user); + if (localStorage.login_user) { + $uname.val(localStorage.login_user); + delete loginStorage.login_user; } [ $uname, $passwd, $confirm] diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index 344a4fb39..c20d53a80 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -124,6 +124,7 @@ define([ } sframeChan.event("EV_SECURE_ACTION", { type: parsed.type, + password: data.password, href: data.url, name: data.name }); diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js index eca166592..49e94a252 100644 --- a/www/secureiframe/main.js +++ b/www/secureiframe/main.js @@ -74,6 +74,7 @@ define([ }; window.addEventListener('message', whenReady); }).nThen(function () { + var isTemplate = config.data.isTemplate; var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); var metaObj; @@ -96,7 +97,7 @@ define([ feedbackAllowed: Utils.Feedback.state, hashes: config.data.hashes, password: config.data.password, - isTemplate: config.data.isTemplate, + isTemplate: isTemplate, file: config.data.file, }; for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } diff --git a/www/slide/inner.js b/www/slide/inner.js index 3af7185c5..de30eb9fc 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -15,6 +15,7 @@ define([ 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', + 'css!/customize/src/print-landscape.css', 'less!/slide/app-slide.less', 'css!cm/lib/codemirror.css', diff --git a/www/teams/inner.js b/www/teams/inner.js index 2eb234b12..a75ed94db 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1267,14 +1267,10 @@ define([ anonRegister = h('button.btn.btn-secondary', Messages.login_register), ])); $(anonLogin).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); }); $(anonRegister).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); }); waitFor.abort(); }).nThen(function () { @@ -1402,9 +1398,7 @@ define([ var hash = privateData.teamInviteHash; if (!hash && !driveAPP.loggedIn) { UI.alert(Messages.mustLogin, function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); }, {forefront: true}); return; }