diff --git a/customize.dist/src/outer.css b/customize.dist/src/outer.css index b36fb2c10..75f449e61 100644 --- a/customize.dist/src/outer.css +++ b/customize.dist/src/outer.css @@ -2,7 +2,7 @@ html, body { margin: 0px; padding: 0px; } -#sbox-iframe, #sbox-share-iframe, #sbox-filePicker-iframe { +#sbox-iframe, #sbox-secure-iframe { position: fixed; top:0; left:0; bottom:0; right:0; diff --git a/www/code/inner.js b/www/code/inner.js index 5b517c74c..28b90b07c 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -278,6 +278,7 @@ define([ if (MEDIA_TAG_MODES.indexOf(mode) !== -1) { // Embedding is endabled framework.setMediaTagEmbedder(function (mt) { + editor.focus(); editor.replaceSelection($(mt)[0].outerHTML); }); } else { diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 4c4518434..b2cd042a8 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -196,6 +196,7 @@ define([ ]); var $frame = $(frame); frame.closeModal = function (cb) { + frame.closeModal = function () {}; // Prevent further calls $frame.fadeOut(150, function () { $frame.detach(); if (typeof(cb) === "function") { cb(); } @@ -464,16 +465,23 @@ define([ UI.createModal = function (cfg) { var $body = cfg.$body || $('body'); - var $blockContainer = $body.find('#'+cfg.id); - if (!$blockContainer.length) { - $blockContainer = $(h('div.cp-modal-container#'+cfg.id, { + var $blockContainer = cfg.id && $body.find('#'+cfg.id); + if (!$blockContainer || !$blockContainer.length) { + var id = ''; + if (cfg.id) { id = '#'+cfg.id; } + $blockContainer = $(h('div.cp-modal-container'+id, { tabindex: 1 })); } + var deleted = false; var hide = function () { - if (cfg.onClose) { return void cfg.onClose(); } + if (deleted) { return; } $blockContainer.hide(); - if (cfg.onClosed) { cfg.onClosed(); } + if (!cfg.id) { + deleted = true; + $blockContainer.remove(); + } + if (cfg.onClose) { cfg.onClose(); } }; $blockContainer.html('').appendTo($body); var $block = $(h('div.cp-modal')).appendTo($blockContainer); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index b7c6310f5..75ee78c15 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1606,11 +1606,7 @@ define([ .text(Messages.accessButton)) .click(common.prepareFeedback(type)) .click(function () { - require(['/common/inner/access.js'], function (Access) { - Access.getAccessModal(common, {}, function (e) { - if (e) { console.error(e); } - }); - }); + sframeChan.event('EV_ACCESS_OPEN'); }); break; case 'properties': @@ -1625,11 +1621,7 @@ define([ if (!data) { return void UI.alert(Messages.autostore_notAvailable); } - require(['/common/inner/properties.js'], function (Properties) { - Properties.getPropertiesModal(common, {}, function (e) { - if (e) { console.error(e); } - }); - }); + sframeChan.event('EV_PROPERTIES_OPEN'); }); }); break; @@ -2642,17 +2634,13 @@ define([ }); }; - UIElements.initFilePicker = function (common, cfg) { - var onSelect = cfg.onSelect || $.noop; + UIElements.openFilePicker = function (common, types, cb) { var sframeChan = common.getSframeChannel(); - sframeChan.on("EV_FILE_PICKED", function (data) { - onSelect(data); + sframeChan.query("Q_FILE_PICKER_OPEN", types, function (err, data) { + if (err) { return; } + cb(data); }); }; - UIElements.openFilePicker = function (common, types) { - var sframeChan = common.getSframeChannel(); - sframeChan.event("EV_FILE_PICKER_OPEN", types); - }; UIElements.openTemplatePicker = function (common, force) { var metadataMgr = common.getMetadataMgr(); @@ -2675,29 +2663,25 @@ define([ return; } delete pickerCfg.hidden; - common.openFilePicker(pickerCfg); var first = true; // We can only pick a template once (for a new document) - var fileDialogCfg = { - onSelect: function (data) { - if (data.type === type && first) { - UI.addLoadingScreen({hideTips: true}); - var chatChan = common.getPadChat(); - var cursorChan = common.getCursorChannel(); - sframeChan.query('Q_TEMPLATE_USE', { - href: data.href, - chat: chatChan, - cursor: cursorChan - }, function () { - first = false; - UI.removeLoadingScreen(); - Feedback.send('TEMPLATE_USED'); - }); - if (focus) { focus.focus(); } - return; - } + common.openFilePicker(pickerCfg, function (data) { + if (data.type === type && first) { + UI.addLoadingScreen({hideTips: true}); + var chatChan = common.getPadChat(); + var cursorChan = common.getCursorChannel(); + sframeChan.query('Q_TEMPLATE_USE', { + href: data.href, + chat: chatChan, + cursor: cursorChan + }, function () { + first = false; + UI.removeLoadingScreen(); + Feedback.send('TEMPLATE_USED'); + }); + if (focus) { focus.focus(); } + return; } - }; - common.initFilePicker(fileDialogCfg); + }); }; sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) { diff --git a/www/common/inner/common-modal.js b/www/common/inner/common-modal.js index ee475c028..33ac14d9f 100644 --- a/www/common/inner/common-modal.js +++ b/www/common/inner/common-modal.js @@ -59,7 +59,7 @@ define([ }), opts.href); // If this is a file, don't try to look for metadata - if (opts.channel && opts.channel.length > 34) { return; } + if (opts.channel && opts.channel.length > 32) { return; } if (opts.channel) { data.channel = opts.channel; } Modal.loadMetadata(Env, data, waitFor); }).nThen(function () { @@ -129,7 +129,10 @@ define([ tabs[i] = { content: c && UI.dialog.customModal(node, { buttons: obj.buttons || button, - onClose: function () { blocked = false; } + onClose: function () { + blocked = false; + if (typeof(opts.onClose) === "function") { opts.onClose(); } + } }), disabled: !c, title: obj.title, diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 3a824214c..20125d82c 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -132,6 +132,12 @@ define([ APP.onLocal(); }; + var isRegisteredUserOnline = function () { + var users = metadataMgr.getMetadata().users || {}; + return Object.keys(users).some(function (id) { + return users[id] && users[id].curvePublic; + }); + }; var isUserOnline = function (ooid) { // Remove ids for users that have left the channel deleteOffline(); @@ -348,16 +354,44 @@ define([ fixSheets(); APP.FM.handleFile(blob, data); }; + + Messages.oo_login = 'Log in...'; // XXX + var noLogin = false; + var makeCheckpoint = function (force) { - if (!common.isLoggedIn()) { return; } var locked = content.saveLock; var lastCp = getLastCp(); var needCp = force || ooChannel.cpIndex % CHECKPOINT_INTERVAL === 0 || - (ooChannel.cpIndex - lastCp.index) > CHECKPOINT_INTERVAL; + (ooChannel.cpIndex - (lastCp.index || 0)) > CHECKPOINT_INTERVAL; if (!needCp) { return; } if (!locked || !isUserOnline(locked) || force) { + if (!common.isLoggedIn() && !isRegisteredUserOnline() && !noLogin) { + var login = h('button.cp-corner-primary', Messages.login_login); + var register = h('button.cp-corner-primary', Messages.login_register); + var cancel = h('button.cp-corner-cancel', Messages.cancel); + 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/'); + }); + modal.delete(); + }); + $(login).click(function () { + common.setLoginRedirect(function () { + common.gotoURL('/login/'); + }); + modal.delete(); + }); + $(cancel).click(function () { + modal.delete(); + noLogin = true; + }); + return; + } + if (!common.isLoggedIn()) { return; } content.saveLock = myOOId; APP.onLocal(); APP.realtime.onSettle(function () { @@ -907,8 +941,16 @@ define([ if (ifr) { ifr.remove(); } }; - common.initFilePicker({ - onSelect: function (data) { + APP.AddImage = function(cb1, cb2) { + APP.AddImageSuccessCallback = cb1; + APP.AddImageErrorCallback = cb2; + common.openFilePicker({ + types: ['file'], + where: ['root'], + filter: { + fileType: ['image/'] + } + }, function (data) { if (data.type !== 'file') { debug("Unexpected data type picked " + data.type); return; @@ -929,19 +971,6 @@ define([ }); }); }); - } - }); - - APP.AddImage = function(cb1, cb2) { - APP.AddImageSuccessCallback = cb1; - APP.AddImageErrorCallback = cb2; - common.openFilePicker({ - types: ['file'], - where: ['root'], - filter: { - fileType: ['image/'] - } - }); }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index da7f043f3..1e41b8e5b 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -675,6 +675,8 @@ define([ sem.take(function (give) { var otherOwners = false; nThen(function (_w) { + // Don't check server metadata for blobs + if (c.length !== 32) { return; } Store.anonRpcMsg(null, { msg: 'GET_METADATA', data: c @@ -1807,6 +1809,7 @@ define([ var cb = Util.once(Util.mkAsync(_cb)); if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); } + if (data.channel.length !== 32) { return void cb({ error: 'EINVAL'}); } store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) { if (err) { return void cb({error: err}); } var metadata = (obj && obj[0]) || {}; diff --git a/www/common/outer/team.js b/www/common/outer/team.js index a2dbf7c05..c151de004 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -678,6 +678,8 @@ define([ sem.take(function (give) { var otherOwners = false; nThen(function (_w) { + // Don't check server metadata for blobs + if (c.length !== 32) { return; } ctx.Store.anonRpcMsg(null, { msg: 'GET_METADATA', data: c diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 171c58b0e..fc149b2cc 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -503,8 +503,13 @@ define([ var createFilePicker = function () { if (!common.isLoggedIn()) { return; } - common.initFilePicker({ - onSelect: function (data) { + $embedButton = common.createButton('mediatag', true).click(function () { + var cfg = { + types: ['file'], + where: ['root'] + }; + if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); } + common.openFilePicker(cfg, function (data) { if (data.type !== 'file') { console.log("Unexpected data type picked " + data.type); return; @@ -516,17 +521,7 @@ define([ var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src; mediaTagEmbedder($(''), data); - } - }); - $embedButton = common.createButton('mediatag', true).click(function () { - var cfg = { - types: ['file'], - where: ['root'] - }; - if ($embedButton.data('filter')) { - cfg.filter = $embedButton.data('filter'); - } - common.openFilePicker(cfg); + }); }).appendTo(toolbar.$rightside).hide(); }; var setMediaTagEmbedder = function (mte, filter) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index f4f42707a..2ae6f789a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -18,8 +18,7 @@ define([ var Cryptget; var SFrameChannel; var sframeChan; - var FilePicker; - var Share; + var SecureIframe; var Messaging; var Notifier; var Utils = { @@ -44,8 +43,7 @@ define([ '/bower_components/chainpad-crypto/crypto.js', '/common/cryptget.js', '/common/outer/worker-channel.js', - '/filepicker/main.js', - '/share/main.js', + '/secureiframe/main.js', '/common/common-messaging.js', '/common/common-notifier.js', '/common/common-hash.js', @@ -58,15 +56,14 @@ define([ '/common/test.js', '/common/userObject.js', ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, - _FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime, + _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; Crypto = Utils.Crypto = _Crypto; Cryptget = _Cryptget; SFrameChannel = _SFrameChannel; - FilePicker = _FilePicker; - Share = _Share; + SecureIframe = _SecureIframe; Messaging = _Messaging; Notifier = _Notifier; Utils.Hash = _Hash; @@ -491,7 +488,7 @@ define([ // Put in the following function the RPC queries that should also work in filepicker - var addCommonRpc = function (sframeChan) { + var addCommonRpc = function (sframeChan, safe) { sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) { Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) { cb({error: err, response: response}); @@ -598,6 +595,12 @@ define([ } if (data.href) { href = data.href; } Cryptpad.getPadAttribute(data.key, function (e, data) { + if (!safe && data) { + // Remove unsafe data for the unsafe iframe + delete data.href; + delete data.roHref; + delete data.password; + } cb({ error: e, data: data @@ -976,81 +979,63 @@ define([ onFileUpload(sframeChan, data, cb); }); - // File picker - var FP = {}; - var initFilePicker = function (cfg) { - // cfg.hidden means pre-loading the filepicker while keeping it hidden. + // Secure modal + var SecureModal = {}; + // Create or display the iframe and modal + var initSecureModal = function (type, cfg, cb) { + cfg.modal = type; + SecureModal.cb = cb; + // cfg.hidden means pre-loading the iframe while keeping it hidden. // if cfg.hidden is true and the iframe already exists, do nothing - if (!FP.$iframe) { + if (!SecureModal.$iframe) { var config = {}; - config.onFilePicked = function (data) { - sframeChan.event('EV_FILE_PICKED', data); + config.onAction = function (data) { + if (typeof(SecureModal.cb) !== "function") { return; } + SecureModal.cb(data); + SecureModal.$iframe.hide(); }; config.onClose = function () { - FP.$iframe.hide(); - }; - config.onFileUpload = onFileUpload; - config.types = cfg; - config.addCommonRpc = addCommonRpc; - config.modules = { - Cryptpad: Cryptpad, - SFrameChannel: SFrameChannel, - Utils: Utils + SecureModal.$iframe.hide(); }; - FP.$iframe = $('', {id: 'sbox-filePicker-iframe'}).appendTo($('body')); - FP.picker = FilePicker.create(config); - } else if (!cfg.hidden) { - FP.$iframe.show(); - FP.picker.refresh(cfg); - } - if (cfg.hidden) { - FP.$iframe.hide(); - return; - } - FP.$iframe.focus(); - }; - sframeChan.on('EV_FILE_PICKER_OPEN', function (data) { - initFilePicker(data); - }); - - // Share modal - var ShareModal = {}; - var initShareModal = function (cfg) { - cfg.hashes = hashes; - cfg.password = password; - cfg.isTemplate = isTemplate; - // cfg.hidden means pre-loading the filepicker while keeping it hidden. - // if cfg.hidden is true and the iframe already exists, do nothing - if (!ShareModal.$iframe) { - var config = {}; - config.onShareAction = function (data) { - sframeChan.event('EV_SHARE_ACTION', data); - }; - config.onClose = function () { - ShareModal.$iframe.hide(); + config.data = { + hashes: hashes, + password: password, + isTemplate: isTemplate }; - config.data = cfg; config.addCommonRpc = addCommonRpc; config.modules = { Cryptpad: Cryptpad, SFrameChannel: SFrameChannel, Utils: Utils }; - ShareModal.$iframe = $('', {id: 'sbox-share-iframe'}).appendTo($('body')); - ShareModal.modal = Share.create(config); + SecureModal.$iframe = $('', {id: 'sbox-secure-iframe'}).appendTo($('body')); + SecureModal.modal = SecureIframe.create(config); } else if (!cfg.hidden) { - ShareModal.modal.refresh(cfg, function () { - ShareModal.$iframe.show(); + SecureModal.modal.refresh(cfg, function () { + SecureModal.$iframe.show(); }); } if (cfg.hidden) { - ShareModal.$iframe.hide(); + SecureModal.$iframe.hide(); return; } - ShareModal.$iframe.focus(); + SecureModal.$iframe.focus(); }; + + sframeChan.on('Q_FILE_PICKER_OPEN', function (data, cb) { + initSecureModal('filepicker', data || {}, cb); + }); + + sframeChan.on('EV_PROPERTIES_OPEN', function (data) { + initSecureModal('properties', data || {}, null); + }); + + sframeChan.on('EV_ACCESS_OPEN', function (data) { + initSecureModal('access', data || {}, null); + }); + sframeChan.on('EV_SHARE_OPEN', function (data) { - initShareModal(data || {}); + initSecureModal('share', data || {}, null); }); sframeChan.on('Q_TEMPLATE_USE', function (data, cb) { @@ -1266,6 +1251,9 @@ define([ var _parsed = Utils.Hash.parsePadUrl(metadata.roHref); _secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password); } + if (_secret.channel.length !== 32) { + return void cb({error: 'EINVAL'}); + } var crypto = Crypto.createEncryptor(_secret.keys); nThen(function (waitFor) { // Try to get the owner's mailbox from the pad metadata first. @@ -1325,6 +1313,9 @@ define([ var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref); _secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password); } + if (_secret.channel.length !== 32) { + return void cb({error: 'EINVAL'}); + } var crypto = Crypto.createEncryptor(_secret.keys); nThen(function (waitFor) { // If we already have metadata, use it, otherwise, try to get it diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 3fa511c58..ad8d6f77e 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -89,7 +89,6 @@ define([ window.CryptPad_UIElements = UIElements; window.CryptPad_common = funcs; funcs.createUserAdminMenu = callWithCommon(UIElements.createUserAdminMenu); - funcs.initFilePicker = callWithCommon(UIElements.initFilePicker); funcs.openFilePicker = callWithCommon(UIElements.openFilePicker); funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker); funcs.displayMediatagImage = callWithCommon(MT.displayMediatagImage); diff --git a/www/filepicker/main.js b/www/filepicker/main.js deleted file mode 100644 index e63ab457c..000000000 --- a/www/filepicker/main.js +++ /dev/null @@ -1,134 +0,0 @@ -// Load #1, load as little as possible because we are in a race to get the loading screen up. -define([ - '/bower_components/nthen/index.js', - '/api/config', - 'jquery', - '/common/requireconfig.js', -], function (nThen, ApiConfig, $, RequireConfig) { - var requireConfig = RequireConfig(); - - var create = function (config) { - // Loaded in load #2 - var sframeChan; - nThen(function (waitFor) { - $(waitFor()); - }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - $('#sbox-filePicker-iframe').attr('src', - ApiConfig.httpSafeOrigin + '/filepicker/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); - }).nThen(function (/*waitFor*/) { - var Cryptpad = config.modules.Cryptpad; - var Utils = config.modules.Utils; - - nThen(function (waitFor) { - // The inner iframe tries to get some data from us every ms (cache, store...). - // It will send a "READY" message and wait for our answer with the correct txid. - // First, we have to answer to this message, otherwise we're going to block - // sframe-boot.js. Then we can start the channel. - var msgEv = Utils.Util.mkEvent(); - var iframe = $('#sbox-filePicker-iframe')[0].contentWindow; - var postMsg = function (data) { - iframe.postMessage(data, '*'); - }; - var w = waitFor(); - var whenReady = function (msg) { - if (msg.source !== iframe) { return; } - var data = JSON.parse(msg.data); - if (!data.txid) { return; } - // Remove the listener once we've received the READY message - window.removeEventListener('message', whenReady); - // Answer with the requested data - postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage(), localStore: window.localStore, cache: window.cpCache })); - - // Then start the channel - window.addEventListener('message', function (msg) { - if (msg.source !== iframe) { return; } - msgEv.fire(msg); - }); - config.modules.SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) { - sframeChan = sfc; - })); - w(); - }; - window.addEventListener('message', whenReady); - }).nThen(function () { - var updateMeta = function () { - //console.log('EV_METADATA_UPDATE'); - var metaObj; - nThen(function (waitFor) { - Cryptpad.getMetadata(waitFor(function (err, n) { - if (err) { console.log(err); } - metaObj = n; - })); - }).nThen(function (/*waitFor*/) { - metaObj.doc = {}; - var additionalPriv = { - fileHost: ApiConfig.fileHost, - loggedIn: Utils.LocalStore.isLoggedIn(), - origin: window.location.origin, - pathname: window.location.pathname, - feedbackAllowed: Utils.Feedback.state, - types: config.types - }; - for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } - - sframeChan.event('EV_METADATA_UPDATE', metaObj); - }); - }; - Cryptpad.onMetadataChanged(updateMeta); - sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); - - config.addCommonRpc(sframeChan); - - sframeChan.on('Q_GET_FILES_LIST', function (types, cb) { - Cryptpad.getSecureFilesList(types, function (err, data) { - cb({ - error: err, - data: data - }); - }); - }); - - sframeChan.on('EV_FILE_PICKER_CLOSE', function () { - config.onClose(); - }); - sframeChan.on('EV_FILE_PICKED', function (data) { - config.onFilePicked(data); - }); - sframeChan.on('Q_UPLOAD_FILE', function (data, cb) { - config.onFileUpload(sframeChan, data, cb); - }); - }); - }); - var refresh = function (types) { - if (!sframeChan) { return; } - sframeChan.event('EV_FILE_PICKER_REFRESH', types); - }; - return { - refresh: refresh - }; - }; - return { - create: create - }; -}); diff --git a/www/poll/inner.js b/www/poll/inner.js index 5c9f9f182..204835555 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -1206,22 +1206,18 @@ define([ updatePublishButton(); if (common.isLoggedIn()) { - var fileDialogCfg = { - onSelect: function (data) { - if (data.type === 'file' && APP.editor) { - var mt = ''; - APP.editor.replaceSelection(mt); - return; - } - } - }; - common.initFilePicker(fileDialogCfg); APP.$mediaTagButton = common.createButton('mediatag', true).click(function () { var pickerCfg = { types: ['file'], where: ['root'] }; - common.openFilePicker(pickerCfg); + common.openFilePicker(pickerCfg, function (data) { + if (data.type === 'file' && APP.editor) { + var mt = ''; + APP.editor.replaceSelection(mt); + return; + } + }); }).appendTo($rightside); var $tags = common.createButton('hashtag', true); diff --git a/www/filepicker/app-filepicker.less b/www/secureiframe/app-secure.less similarity index 84% rename from www/filepicker/app-filepicker.less rename to www/secureiframe/app-secure.less index c0b9e7789..bece9d4c0 100644 --- a/www/filepicker/app-filepicker.less +++ b/www/secureiframe/app-secure.less @@ -6,8 +6,11 @@ @import (reference) '../../customize/src/less2/include/tippy.less'; @import (reference) '../../customize/src/less2/include/checkmark.less'; @import (reference) '../../customize/src/less2/include/password-input.less'; +@import (reference) '../../customize/src/less2/include/modals-ui-elements.less'; +@import (reference) '../../customize/src/less2/include/usergrid.less'; -&.cp-app-filepicker { +&.cp-app-secureiframe { + .modals-ui-elements_main(); .iconColors_main(); .fileupload_main(); .alertify_main(); @@ -15,6 +18,7 @@ .checkmark_main(20px); .password_main(); .modal_main(); + .usergrid_main(); #cp-filepicker-dialog { display: none; @@ -26,6 +30,14 @@ overflow-y: auto; } + .cp-loading-spinner-container { + height: 100px; + .cp-spinner { + border-color: @colortheme_logo-2; + border-top-color: transparent; + } + } + .cp-filepicker-content-element { width: 125px; //min-width: 200px; diff --git a/www/filepicker/inner.html b/www/secureiframe/inner.html similarity index 73% rename from www/filepicker/inner.html rename to www/secureiframe/inner.html index 8090d7abc..29c3cf797 100644 --- a/www/filepicker/inner.html +++ b/www/secureiframe/inner.html @@ -2,7 +2,7 @@ - + - + diff --git a/www/filepicker/inner.js b/www/secureiframe/inner.js similarity index 56% rename from www/filepicker/inner.js rename to www/secureiframe/inner.js index 205a0b8db..dc8f5af3f 100644 --- a/www/filepicker/inner.js +++ b/www/secureiframe/inner.js @@ -7,12 +7,13 @@ define([ '/common/common-ui-elements.js', '/common/common-util.js', '/common/common-hash.js', + '/common/hyperscript.js', 'json.sortify', '/customize/messages.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', - 'less!/filepicker/app-filepicker.less', + 'less!/secureiframe/app-secure.less', ], function ( $, Crypto, @@ -22,6 +23,7 @@ define([ UIElements, Util, Hash, + h, Sortify, Messages) { @@ -29,23 +31,88 @@ define([ var andThen = function (common) { var metadataMgr = common.getMetadataMgr(); - var privateData = metadataMgr.getPrivateData(); - var $body = $('body'); var sframeChan = common.getSframeChannel(); - var filters = metadataMgr.getPrivateData().types; + var $body = $('body'); + + var hideIframe = function () { + sframeChan.event('EV_SECURE_IFRAME_CLOSE'); + }; + + var displayed; + var create = {}; + + // Share modal + create['share'] = function (data) { + var priv = metadataMgr.getPrivateData(); + var f = (data && data.file) ? UIElements.createFileShareModal + : UIElements.createShareModal; + + var friends = common.getFriends(); + + var _modal; + var modal = f({ + origin: priv.origin, + pathname: priv.pathname, + password: priv.password, + isTemplate: priv.isTemplate, + hashes: priv.hashes, + common: common, + title: data.title, + friends: friends, + onClose: function () { + if (_modal && _modal.close) { _modal.close(); } + hideIframe(); + }, + fileData: { + hash: priv.hashes.fileHash, + password: priv.password + } + }); + $('button.cancel').click(); // Close any existing alertify + _modal = UI.openCustomModal(modal); + displayed = modal; + }; + + // Properties modal + create['properties'] = function () { + require(['/common/inner/properties.js'], function (Properties) { + Properties.getPropertiesModal(common, { + onClose: function () { + hideIframe(); + } + }, function (e, modal) { + if (e) { console.error(e); } + displayed = modal; + }); + }); + }; - var hideFileDialog = function () { - sframeChan.event('EV_FILE_PICKER_CLOSE'); + // Access modal + create['access'] = function () { + require(['/common/inner/access.js'], function (Access) { + Access.getAccessModal(common, { + onClose: function () { + hideIframe(); + } + }, function (e, modal) { + if (e) { console.error(e); } + displayed = modal; + }); + }); }; + + // File uploader var onFilePicked = function (data) { + var privateData = metadataMgr.getPrivateData(); var parsed = Hash.parsePadUrl(data.url); - hideFileDialog(); + if (displayed && displayed.hide) { displayed.hide(); } + hideIframe(); if (parsed.type === 'file') { var secret = Hash.getSecrets('file', parsed.hash, data.password); var fileHost = privateData.fileHost || privateData.origin; var src = fileHost + Hash.getBlobPathFromHex(secret.channel); var key = Hash.encodeBase64(secret.keys.cryptKey); - sframeChan.event("EV_FILE_PICKED", { + sframeChan.event("EV_SECURE_ACTION", { type: parsed.type, src: src, name: data.name, @@ -53,14 +120,12 @@ define([ }); return; } - sframeChan.event("EV_FILE_PICKED", { + sframeChan.event("EV_SECURE_ACTION", { type: parsed.type, href: data.url, name: data.name }); }; - - // File uploader var fmConfig = { body: $('body'), noHandlers: true, @@ -69,40 +134,40 @@ define([ } }; APP.FM = common.createFileManager(fmConfig); + create['filepicker'] = function (_filters) { + var updateContainer = function () {}; - // Create file picker - var onSelect = function (url, name, password) { - onFilePicked({url: url, name: name, password: password}); - }; - var data = { - FM: APP.FM - }; - var updateContainer; - var createFileDialog = function () { + var filters = _filters; var types = filters.types || []; + var data = { + FM: APP.FM + }; + // Create modal var modal = UI.createModal({ - id: 'cp-filepicker-dialog', $body: $body, - onClose: hideFileDialog + onClose: function () { + hideIframe(); + } }); + displayed = modal; modal.show(); - var $blockContainer = modal.$modal; + // Set the fixed content - var $block = $blockContainer.find('.cp-modal'); + modal.$modal.attr('id', 'cp-filepicker-dialog'); + var $block = modal.$modal.find('.cp-modal'); // Description var text = Messages.filePicker_description; if (types && types.length === 1 && types[0] !== 'file') { - // Should be Templates text = Messages.selectTemplate; } - var $description = $('').text(text); - $block.append($description); + $block.append(h('p', text)); - var $filter = $('', {'class': 'cp-modal-form'}).appendTo($block); + // Add filter input + var $filter = $(h('p.cp-modal-form')).appendTo($block); var to; - $('', { + var $input = $('', { type: 'text', 'class': 'cp-filepicker-filter', 'placeholder': Messages.filePicker_filter @@ -111,7 +176,7 @@ define([ to = window.setTimeout(updateContainer, 300); }); - //If file, display the upload button + // If file, display the upload button if (types.indexOf('file') !== -1 && common.isLoggedIn()) { var f = (filters && filters.filter) || {}; delete data.accept; @@ -126,15 +191,16 @@ define([ $filter.append(common.createButton('upload', false, data)); } - var $container = $('', {'class': 'cp-filepicker-content'}).appendTo($block); + var $container = $(h('span.cp-filepicker-content', [ + h('div.cp-loading-spinner-container', h('span.cp-spinner')) + ])).appendTo($block); // Update the files list when needed updateContainer = function () { - $container.html(''); - var $input = $filter.find('.cp-filepicker-filter'); var filter = $input.val().trim(); var todo = function (err, list) { if (err) { return void console.error(err); } + $container.html(''); Object.keys(list).forEach(function (id) { var data = list[id]; var name = data.filename || data.title || '?'; @@ -149,8 +215,8 @@ define([ $('', {'class': 'cp-filepicker-content-element-name'}).text(name) .appendTo($span); $span.click(function () { - if (typeof onSelect === "function") { - onSelect(data.href, name, data.password); + if (typeof onFilePicked === "function") { + onFilePicked({url: data.href, name: name, password: data.password}); } }); @@ -163,21 +229,23 @@ define([ }; updateContainer(); }; - sframeChan.on('EV_FILE_PICKER_REFRESH', function (newFilters) { - if (Sortify(filters) !== Sortify(newFilters)) { - $body.html(''); - filters = newFilters; - return void createFileDialog(); - } - updateContainer(); + + sframeChan.on('EV_REFRESH', function (data) { + if (!data) { return; } + var type = data.modal; + if (!create[type]) { return; } + if (displayed && displayed.close) { displayed.close(); } + else if (displayed && displayed.hide) { displayed.hide(); } + displayed = undefined; + create[type](data); }); - createFileDialog(); UI.removeLoadingScreen(); }; var main = function () { var common; + var _andThen = Util.once(andThen); nThen(function (waitFor) { $(waitFor(function () { @@ -187,11 +255,11 @@ define([ }).nThen(function (/*waitFor*/) { var metadataMgr = common.getMetadataMgr(); if (metadataMgr.getMetadataLazy() !== 'uninitialized') { - andThen(common); + _andThen(common); return; } metadataMgr.onChange(function () { - andThen(common); + _andThen(common); }); }); }; diff --git a/www/share/main.js b/www/secureiframe/main.js similarity index 85% rename from www/share/main.js rename to www/secureiframe/main.js index f1f9a3a3a..ced68bbc6 100644 --- a/www/share/main.js +++ b/www/secureiframe/main.js @@ -22,8 +22,8 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-share-iframe').attr('src', - ApiConfig.httpSafeOrigin + '/share/inner.html?' + requireConfig.urlArgs + + $('#sbox-secure-iframe').attr('src', + ApiConfig.httpSafeOrigin + '/secureiframe/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); // This is a cheap trick to avoid loading sframe-channel in parallel with the @@ -48,7 +48,7 @@ define([ // First, we have to answer to this message, otherwise we're going to block // sframe-boot.js. Then we can start the channel. var msgEv = Utils.Util.mkEvent(); - var iframe = $('#sbox-share-iframe')[0].contentWindow; + var iframe = $('#sbox-secure-iframe')[0].contentWindow; var postMsg = function (data) { iframe.postMessage(data, '*'); }; @@ -106,7 +106,11 @@ define([ Cryptpad.onMetadataChanged(updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); - config.addCommonRpc(sframeChan); + config.addCommonRpc(sframeChan, true); + + Cryptpad.padRpc.onMetadataEvent.reg(function (data) { + sframeChan.event('EV_RT_METADATA', data); + }); sframeChan.on('EV_CACHE_PUT', function (x) { Object.keys(x).forEach(function (k) { @@ -132,11 +136,24 @@ define([ }); }); - sframeChan.on('EV_SHARE_CLOSE', function () { - config.onClose(); + sframeChan.on('EV_SECURE_ACTION', function (data) { + config.onAction(data); + }); + sframeChan.on('Q_UPLOAD_FILE', function (data, cb) { + config.onFileUpload(sframeChan, data, cb); }); - sframeChan.on('EV_SHARE_ACTION', function (data) { - config.onShareAction(data); + + sframeChan.on('Q_GET_FILES_LIST', function (types, cb) { + Cryptpad.getSecureFilesList(types, function (err, data) { + cb({ + error: err, + data: data + }); + }); + }); + + sframeChan.on('EV_SECURE_IFRAME_CLOSE', function () { + config.onClose(); }); sframeChan.onReady(function () { @@ -155,7 +172,7 @@ define([ }; return; } - sframeChan.event('EV_SHARE_REFRESH', data); + sframeChan.event('EV_REFRESH', data); cb(); }; return { diff --git a/www/share/app-share.less b/www/share/app-share.less deleted file mode 100644 index 8efdd17f3..000000000 --- a/www/share/app-share.less +++ /dev/null @@ -1,17 +0,0 @@ -@import (reference) '../../customize/src/less2/include/tippy.less'; -@import (reference) '../../customize/src/less2/include/modal.less'; -@import (reference) '../../customize/src/less2/include/alertify.less'; -@import (reference) '../../customize/src/less2/include/checkmark.less'; -@import (reference) '../../customize/src/less2/include/password-input.less'; -@import (reference) '../../customize/src/less2/include/usergrid.less'; -@import (reference) '../../customize/src/less2/include/modals-ui-elements.less'; - -&.cp-app-share { - .tippy_main(); - .alertify_main(); - .modals-ui-elements_main(); - .checkmark_main(20px); - .password_main(); - .modal_main(); - .usergrid_main(); -} diff --git a/www/share/index.html b/www/share/index.html deleted file mode 100644 index 8be90cfb5..000000000 --- a/www/share/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - CryptPad - - - - - - - - \ No newline at end of file diff --git a/www/share/inner.html b/www/share/inner.html deleted file mode 100644 index 6cf7179ae..000000000 --- a/www/share/inner.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/www/share/inner.js b/www/share/inner.js deleted file mode 100644 index f02af4b8b..000000000 --- a/www/share/inner.js +++ /dev/null @@ -1,87 +0,0 @@ -define([ - 'jquery', - '/bower_components/nthen/index.js', - '/common/sframe-common.js', - '/common/common-ui-elements.js', - '/common/common-interface.js', - - 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', - 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', - 'less!/share/app-share.less', -], function ( - $, - nThen, - SFCommon, - UIElements, - UI) -{ - var APP = window.APP = {}; - - var init = false; - var andThen = function (common) { - if (init) { return; } - init = true; - - var metadataMgr = common.getMetadataMgr(); - var sframeChan = common.getSframeChannel(); - - var hideShareDialog = function () { - sframeChan.event('EV_SHARE_CLOSE'); - }; - - var createShareDialog = function (data) { - var priv = metadataMgr.getPrivateData(); - var hashes = priv.hashes; - var origin = priv.origin; - var pathname = priv.pathname; - var f = (data && data.file) ? UIElements.createFileShareModal - : UIElements.createShareModal; - - var friends = common.getFriends(); - - var modal = f({ - origin: origin, - pathname: pathname, - password: priv.password, - isTemplate: priv.isTemplate, - hashes: hashes, - common: common, - title: data.title, - friends: friends, - onClose: function () { - hideShareDialog(); - }, - fileData: { - hash: hashes.fileHash, - password: priv.password - } - }); - $('button.cancel').click(); // Close any existing alertify - UI.openCustomModal(modal); - }; - sframeChan.on('EV_SHARE_REFRESH', function (data) { - createShareDialog(data); - }); - }; - - var main = function () { - var common; - - nThen(function (waitFor) { - $(waitFor(function () { - UI.removeLoadingScreen(); - })); - SFCommon.create(waitFor(function (c) { APP.common = common = c; })); - }).nThen(function (/*waitFor*/) { - var metadataMgr = common.getMetadataMgr(); - if (metadataMgr.getMetadataLazy() !== 'uninitialized') { - andThen(common); - return; - } - metadataMgr.onChange(function () { - andThen(common); - }); - }); - }; - main(); -});
').text(text); - $block.append($description); + $block.append(h('p', text)); - var $filter = $('
', {'class': 'cp-modal-form'}).appendTo($block); + // Add filter input + var $filter = $(h('p.cp-modal-form')).appendTo($block); var to; - $('', { + var $input = $('', { type: 'text', 'class': 'cp-filepicker-filter', 'placeholder': Messages.filePicker_filter @@ -111,7 +176,7 @@ define([ to = window.setTimeout(updateContainer, 300); }); - //If file, display the upload button + // If file, display the upload button if (types.indexOf('file') !== -1 && common.isLoggedIn()) { var f = (filters && filters.filter) || {}; delete data.accept; @@ -126,15 +191,16 @@ define([ $filter.append(common.createButton('upload', false, data)); } - var $container = $('', {'class': 'cp-filepicker-content'}).appendTo($block); + var $container = $(h('span.cp-filepicker-content', [ + h('div.cp-loading-spinner-container', h('span.cp-spinner')) + ])).appendTo($block); // Update the files list when needed updateContainer = function () { - $container.html(''); - var $input = $filter.find('.cp-filepicker-filter'); var filter = $input.val().trim(); var todo = function (err, list) { if (err) { return void console.error(err); } + $container.html(''); Object.keys(list).forEach(function (id) { var data = list[id]; var name = data.filename || data.title || '?'; @@ -149,8 +215,8 @@ define([ $('', {'class': 'cp-filepicker-content-element-name'}).text(name) .appendTo($span); $span.click(function () { - if (typeof onSelect === "function") { - onSelect(data.href, name, data.password); + if (typeof onFilePicked === "function") { + onFilePicked({url: data.href, name: name, password: data.password}); } }); @@ -163,21 +229,23 @@ define([ }; updateContainer(); }; - sframeChan.on('EV_FILE_PICKER_REFRESH', function (newFilters) { - if (Sortify(filters) !== Sortify(newFilters)) { - $body.html(''); - filters = newFilters; - return void createFileDialog(); - } - updateContainer(); + + sframeChan.on('EV_REFRESH', function (data) { + if (!data) { return; } + var type = data.modal; + if (!create[type]) { return; } + if (displayed && displayed.close) { displayed.close(); } + else if (displayed && displayed.hide) { displayed.hide(); } + displayed = undefined; + create[type](data); }); - createFileDialog(); UI.removeLoadingScreen(); }; var main = function () { var common; + var _andThen = Util.once(andThen); nThen(function (waitFor) { $(waitFor(function () { @@ -187,11 +255,11 @@ define([ }).nThen(function (/*waitFor*/) { var metadataMgr = common.getMetadataMgr(); if (metadataMgr.getMetadataLazy() !== 'uninitialized') { - andThen(common); + _andThen(common); return; } metadataMgr.onChange(function () { - andThen(common); + _andThen(common); }); }); }; diff --git a/www/share/main.js b/www/secureiframe/main.js similarity index 85% rename from www/share/main.js rename to www/secureiframe/main.js index f1f9a3a3a..ced68bbc6 100644 --- a/www/share/main.js +++ b/www/secureiframe/main.js @@ -22,8 +22,8 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-share-iframe').attr('src', - ApiConfig.httpSafeOrigin + '/share/inner.html?' + requireConfig.urlArgs + + $('#sbox-secure-iframe').attr('src', + ApiConfig.httpSafeOrigin + '/secureiframe/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); // This is a cheap trick to avoid loading sframe-channel in parallel with the @@ -48,7 +48,7 @@ define([ // First, we have to answer to this message, otherwise we're going to block // sframe-boot.js. Then we can start the channel. var msgEv = Utils.Util.mkEvent(); - var iframe = $('#sbox-share-iframe')[0].contentWindow; + var iframe = $('#sbox-secure-iframe')[0].contentWindow; var postMsg = function (data) { iframe.postMessage(data, '*'); }; @@ -106,7 +106,11 @@ define([ Cryptpad.onMetadataChanged(updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); - config.addCommonRpc(sframeChan); + config.addCommonRpc(sframeChan, true); + + Cryptpad.padRpc.onMetadataEvent.reg(function (data) { + sframeChan.event('EV_RT_METADATA', data); + }); sframeChan.on('EV_CACHE_PUT', function (x) { Object.keys(x).forEach(function (k) { @@ -132,11 +136,24 @@ define([ }); }); - sframeChan.on('EV_SHARE_CLOSE', function () { - config.onClose(); + sframeChan.on('EV_SECURE_ACTION', function (data) { + config.onAction(data); + }); + sframeChan.on('Q_UPLOAD_FILE', function (data, cb) { + config.onFileUpload(sframeChan, data, cb); }); - sframeChan.on('EV_SHARE_ACTION', function (data) { - config.onShareAction(data); + + sframeChan.on('Q_GET_FILES_LIST', function (types, cb) { + Cryptpad.getSecureFilesList(types, function (err, data) { + cb({ + error: err, + data: data + }); + }); + }); + + sframeChan.on('EV_SECURE_IFRAME_CLOSE', function () { + config.onClose(); }); sframeChan.onReady(function () { @@ -155,7 +172,7 @@ define([ }; return; } - sframeChan.event('EV_SHARE_REFRESH', data); + sframeChan.event('EV_REFRESH', data); cb(); }; return { diff --git a/www/share/app-share.less b/www/share/app-share.less deleted file mode 100644 index 8efdd17f3..000000000 --- a/www/share/app-share.less +++ /dev/null @@ -1,17 +0,0 @@ -@import (reference) '../../customize/src/less2/include/tippy.less'; -@import (reference) '../../customize/src/less2/include/modal.less'; -@import (reference) '../../customize/src/less2/include/alertify.less'; -@import (reference) '../../customize/src/less2/include/checkmark.less'; -@import (reference) '../../customize/src/less2/include/password-input.less'; -@import (reference) '../../customize/src/less2/include/usergrid.less'; -@import (reference) '../../customize/src/less2/include/modals-ui-elements.less'; - -&.cp-app-share { - .tippy_main(); - .alertify_main(); - .modals-ui-elements_main(); - .checkmark_main(20px); - .password_main(); - .modal_main(); - .usergrid_main(); -} diff --git a/www/share/index.html b/www/share/index.html deleted file mode 100644 index 8be90cfb5..000000000 --- a/www/share/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - CryptPad - - - - - - - - \ No newline at end of file diff --git a/www/share/inner.html b/www/share/inner.html deleted file mode 100644 index 6cf7179ae..000000000 --- a/www/share/inner.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/www/share/inner.js b/www/share/inner.js deleted file mode 100644 index f02af4b8b..000000000 --- a/www/share/inner.js +++ /dev/null @@ -1,87 +0,0 @@ -define([ - 'jquery', - '/bower_components/nthen/index.js', - '/common/sframe-common.js', - '/common/common-ui-elements.js', - '/common/common-interface.js', - - 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', - 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', - 'less!/share/app-share.less', -], function ( - $, - nThen, - SFCommon, - UIElements, - UI) -{ - var APP = window.APP = {}; - - var init = false; - var andThen = function (common) { - if (init) { return; } - init = true; - - var metadataMgr = common.getMetadataMgr(); - var sframeChan = common.getSframeChannel(); - - var hideShareDialog = function () { - sframeChan.event('EV_SHARE_CLOSE'); - }; - - var createShareDialog = function (data) { - var priv = metadataMgr.getPrivateData(); - var hashes = priv.hashes; - var origin = priv.origin; - var pathname = priv.pathname; - var f = (data && data.file) ? UIElements.createFileShareModal - : UIElements.createShareModal; - - var friends = common.getFriends(); - - var modal = f({ - origin: origin, - pathname: pathname, - password: priv.password, - isTemplate: priv.isTemplate, - hashes: hashes, - common: common, - title: data.title, - friends: friends, - onClose: function () { - hideShareDialog(); - }, - fileData: { - hash: hashes.fileHash, - password: priv.password - } - }); - $('button.cancel').click(); // Close any existing alertify - UI.openCustomModal(modal); - }; - sframeChan.on('EV_SHARE_REFRESH', function (data) { - createShareDialog(data); - }); - }; - - var main = function () { - var common; - - nThen(function (waitFor) { - $(waitFor(function () { - UI.removeLoadingScreen(); - })); - SFCommon.create(waitFor(function (c) { APP.common = common = c; })); - }).nThen(function (/*waitFor*/) { - var metadataMgr = common.getMetadataMgr(); - if (metadataMgr.getMetadataLazy() !== 'uninitialized') { - andThen(common); - return; - } - metadataMgr.onChange(function () { - andThen(common); - }); - }); - }; - main(); -});