diff --git a/www/code/app-code.less b/www/code/app-code.less index 4ed6e2837..8772a2653 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -18,6 +18,7 @@ .CodeMirror { display: inline-block; height: 100%; + min-height: 100%; width: 50%; min-width: 20%; max-width: 80%; diff --git a/www/common/common-realtime.js b/www/common/common-realtime.js index c9dd74b29..ab7d0abfb 100644 --- a/www/common/common-realtime.js +++ b/www/common/common-realtime.js @@ -19,38 +19,39 @@ define([ if (typeof(realtime.getAuthDoc) !== 'function') { return void console.error('improper use of this function'); } - window.setTimeout(function () { if (realtime.getAuthDoc() === realtime.getUserDoc()) { return void cb(); } else { realtime.onSettle(cb); } - - if (intr) { return; } - intr = window.setInterval(function () { - var l; - try { - l = realtime.getLag(); - } catch (e) { - throw new Error("ChainPad.getLag() does not exist, please `bower update`"); - } - if (l.lag < BAD_STATE_TIMEOUT || !connected) { return; } - realtime.abort(); - // don't launch more than one popup - if (common.infiniteSpinnerDetected) { return; } - infiniteSpinnerHandlers.forEach(function (ish) { ish(); }); - - // inform the user their session is in a bad state - Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) { - if (!yes) { return; } - window.parent.location.reload(); - }); - common.infiniteSpinnerDetected = true; - }, 2000); }, 0); }; + common.beginDetectingInfiniteSpinner = function (Cryptpad, realtime) { + if (intr) { return; } + intr = window.setInterval(function () { + var l; + try { + l = realtime.getLag(); + } catch (e) { + throw new Error("ChainPad.getLag() does not exist, please `bower update`"); + } + if (l.lag < BAD_STATE_TIMEOUT || !connected) { return; } + realtime.abort(); + // don't launch more than one popup + if (common.infiniteSpinnerDetected) { return; } + infiniteSpinnerHandlers.forEach(function (ish) { ish(); }); + + // inform the user their session is in a bad state + Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) { + if (!yes) { return; } + window.parent.location.reload(); + }); + common.infiniteSpinnerDetected = true; + }, 2000); + }; + common.onInfiniteSpinner = function (f) { infiniteSpinnerHandlers.push(f); }; common.setConnectionState = function (bool) { diff --git a/www/common/common-util.js b/www/common/common-util.js index e42a0249c..aed4dd5d2 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -1,6 +1,28 @@ define([], function () { var Util = {}; + // If once is true, after the event has been fired, any further handlers which are + // registered will fire immediately, and this type of event cannot be fired twice. + Util.mkEvent = function (once) { + var handlers = []; + var fired = false; + return { + reg: function (cb) { + if (once && fired) { return void setTimeout(cb); } + handlers.push(cb); + }, + unreg: function (cb) { + if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); } + handlers.splice(handlers.indexOf(cb), 1); + }, + fire: function () { + if (fired) { return; } + fired = true; + handlers.forEach(function (h) { h(); }); + } + }; + }; + Util.find = function (map, path) { return (map && path.reduce(function (p, n) { return typeof(p[n]) !== 'undefined' && p[n]; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1cb14144b..a3cd707a0 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -138,6 +138,10 @@ define([ Realtime.whenRealtimeSyncs(common, realtime, cb); }; + common.beginDetectingInfiniteSpinner = function (realtime) { + Realtime.beginDetectingInfiniteSpinner(common, realtime); + }; + // Userlist common.createUserList = UserList.create; diff --git a/www/common/migrate-user-object.js b/www/common/migrate-user-object.js index cc088c7c5..fd469b07c 100644 --- a/www/common/migrate-user-object.js +++ b/www/common/migrate-user-object.js @@ -44,57 +44,38 @@ define([], function () { userObject.version = version = 1; } - - // Migration 2: indentation settings for CodeMirror moved from root to 'settings' - var migrateIndent = function () { - var indentKey = 'cryptpad.indentUnit'; - var useTabsKey = 'cryptpad.indentWithTabs'; - userObject.settings = userObject.settings || {}; - if (userObject[indentKey]) { - userObject.settings.indentUnit = userObject[indentKey]; - delete userObject[indentKey]; - } - if (userObject[useTabsKey]) { - userObject.settings.indentWithTabs = userObject[useTabsKey]; - delete userObject[useTabsKey]; - } - }; - if (version < 2) { - migrateIndent(); - Cryptpad.feedback('Migrate-2', true); - userObject.version = version = 2; - } - - - // Migration 3: global attributes from root to 'settings' subobjects + // Migration 2: global attributes from root to 'settings' subobjects var migrateAttributes = function () { var drawer = 'cryptpad.userlist-drawer'; var polls = 'cryptpad.hide_poll_text'; - var indentKey = 'indentUnit'; - var useTabsKey = 'indentWithTabs'; + var indentKey = 'cryptpad.indentUnit'; + var useTabsKey = 'cryptpad.indentWithTabs'; var settings = userObject.settings = userObject.settings || {}; - if (settings[indentKey] || settings[useTabsKey]) { + if (typeof(userObject[indentKey]) !== "undefined") { settings.codemirror = settings.codemirror || {}; - settings.codemirror.indentUnit = settings[indentKey]; - settings.codemirror.indentWithTabs = settings[useTabsKey]; - delete settings[indentKey]; - delete settings[useTabsKey]; + settings.codemirror.indentUnit = userObject[indentKey]; + delete userObject[indentKey]; } - if (userObject[drawer]) { + if (typeof(userObject[useTabsKey]) !== "undefined") { + settings.codemirror = settings.codemirror || {}; + settings.codemirror.indentWithTabs = userObject[useTabsKey]; + delete userObject[useTabsKey]; + } + if (typeof(userObject[drawer]) !== "undefined") { settings.toolbar = settings.toolbar || {}; settings.toolbar['userlist-drawer'] = userObject[drawer]; delete userObject[drawer]; } - if (userObject[polls]) { + if (typeof(userObject[polls]) !== "undefined") { settings.poll = settings.poll || {}; settings.poll['hide-text'] = userObject[polls]; delete userObject[polls]; } }; - if (version < 3) { + if (version < 2) { migrateAttributes(); - Cryptpad.feedback('Migrate-3', true); - userObject.version = version = 3; + Cryptpad.feedback('Migrate-2', true); + userObject.version = version = 2; } }; }); diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 04659fe17..787da1ca3 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -15,11 +15,16 @@ * along with this program. If not, see . */ define([ + '/common/common-util.js', + '/customize/application_config.js', '/bower_components/chainpad/chainpad.dist.js' -], function () { +], function (Util, AppConfig) { var ChainPad = window.ChainPad; var module = { exports: {} }; + var badStateTimeout = typeof(AppConfig.badStateTimeout) === 'number' ? + AppConfig.badStateTimeout : 30000; + var verbose = function (x) { console.log(x); }; verbose = function () {}; // comment out to enable verbose logging @@ -44,9 +49,25 @@ define([ var chainpad; var myID; var isReady = false; + var evConnected = Util.mkEvent(true); + var evInfiniteSpinner = Util.mkEvent(true); + + window.setInterval(function () { + if (!chainpad || !myID) { return; } + var l; + try { + l = chainpad.getLag(); + } catch (e) { + throw new Error("ChainPad.getLag() does not exist, please `bower update`"); + } + if (l.lag < badStateTimeout) { return; } + chainpad.abort(); + evInfiniteSpinner.fire(); + }, 2000); sframeChan.on('EV_RT_DISCONNECT', function () { isReady = false; + if (chainpad) { chainpad.abort(); } onConnectionChange({ state: false }); }); sframeChan.on('EV_RT_CONNECT', function (content) { @@ -55,6 +76,7 @@ define([ isReady = false; if (chainpad) { // it's a reconnect + if (chainpad) { chainpad.start(); } onConnectionChange({ state: true, myId: myID }); return; } @@ -77,6 +99,7 @@ define([ realtime: chainpad, readOnly: readOnly }); + evConnected.fire(); }); sframeChan.on('Q_RT_MESSAGE', function (content, cb) { if (isReady) { @@ -92,9 +115,22 @@ define([ setMyID({ myID: myID }); onReady({ realtime: chainpad }); }); + + var whenRealtimeSyncs = function (cb) { + evConnected.reg(function () { + if (chainpad.getAuthDoc() === chainpad.getUserDoc()) { + return void cb(); + } else { + chainpad.onSettle(cb); + } + }); + }; + return Object.freeze({ getMyID: function () { return myID; }, - metadataMgr: metadataMgr + metadataMgr: metadataMgr, + whenRealtimeSyncs: whenRealtimeSyncs, + onInfiniteSpinner: evInfiniteSpinner.reg }); }; return Object.freeze(module.exports); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 1041884b5..6b6528f79 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -333,8 +333,16 @@ define([ cb(hasTemplate); }); + sframeChan.on('EV_GOTO_URL', function (url) { + if (url) { + window.location.href = url; + } else { + window.location.reload(); + } + }); sframeChan.ready(); + CpNfOuter.start({ sframeChan: sframeChan, channel: secret.channel, diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 1a70446f9..3e6cb48d1 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -13,10 +13,25 @@ define([ '/customize/application_config.js', '/common/cryptpad-common.js', - '/common/common-realtime.js' -], function ($, nThen, Messages, CpNfInner, SFrameChannel, Title, UI, History, File, CodeMirror, + '/common/common-realtime.js', + '/common/common-util.js' +], function ( + $, + nThen, + Messages, + CpNfInner, + SFrameChannel, + Title, + UI, + History, + File, + CodeMirror, MetadataMgr, - AppConfig, Cryptpad, CommonRealtime) { + AppConfig, + Cryptpad, + CommonRealtime, + Util +) { // Chainpad Netflux Inner var funcs = {}; @@ -24,12 +39,15 @@ define([ funcs.Messages = Messages; + var evRealtimeSynced = Util.mkEvent(true); + funcs.startRealtime = function (options) { if (ctx.cpNfInner) { return ctx.cpNfInner; } options.sframeChan = ctx.sframeChan; options.metadataMgr = ctx.metadataMgr; ctx.cpNfInner = CpNfInner.start(options); ctx.cpNfInner.metadataMgr.onChangeLazy(options.onLocal); + ctx.cpNfInner.whenRealtimeSyncs(function () { evRealtimeSynced.fire(); }); return ctx.cpNfInner; }; @@ -210,6 +228,10 @@ define([ }); }; */ + funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); }; + + funcs.whenRealtimeSyncs = evRealtimeSynced.reg; + Object.freeze(funcs); return { create: function (cb) { nThen(function (waitFor) { diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 4e2be59ec..1be9d7476 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -121,6 +121,9 @@ define({ '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, + // Present mode URL 'Q_PRESENT_URL_GET_VALUE': true, 'EV_PRESENT_URL_SET_VALUE': true, diff --git a/www/common/toolbar2.js b/www/common/toolbar2.js index c79a9c233..c0a8f7c6b 100644 --- a/www/common/toolbar2.js +++ b/www/common/toolbar2.js @@ -711,6 +711,7 @@ define([ }, local ? 0 : SPINNER_DISAPPEAR_TIME); }; if (Cryptpad) { + Cryptpad.beginDetectingInfiniteSpinner(config.realtime); Cryptpad.whenRealtimeSyncs(config.realtime, onSynced); return; } @@ -733,6 +734,9 @@ define([ // receive a patch. if (Cryptpad) { typing = 0; + // We're just placing this detector here because it used to be triggered by + // whenRealtimeSyncs() and now it is not because in sframe it is handled differently. + Cryptpad.beginDetectingInfiniteSpinner(config.realtime); Cryptpad.whenRealtimeSyncs(config.realtime, function () { kickSpinner(toolbar, config); }); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 1ffc5c4e8..58e4e1eec 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -673,11 +673,7 @@ define([ $spin.text(Messages.saved); }, local ? 0 : SPINNER_DISAPPEAR_TIME); }; - if (Cryptpad) { - Cryptpad.whenRealtimeSyncs(config.realtime, onSynced); - return; - } - onSynced(); + config.sfCommon.whenRealtimeSyncs(onSynced); }; var ks = function (toolbar, config, local) { return function () { @@ -694,12 +690,10 @@ define([ } // without this, users in read-only mode say 'synchronizing' until they // receive a patch. - if (Cryptpad) { - typing = 0; - Cryptpad.whenRealtimeSyncs(config.realtime, function () { - kickSpinner(toolbar, config); - }); - } + typing = 0; + config.sfCommon.whenRealtimeSyncs(function () { + kickSpinner(toolbar, config); + }); return $spin; }; diff --git a/www/pad/inner.js b/www/pad/inner.js index c1d4d17de..3bb73d6ad 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -33,7 +33,6 @@ define([ '/bower_components/nthen/index.js', '/common/sframe-common.js', '/api/config', - '/common/common-realtime.js', '/bower_components/file-saver/FileSaver.min.js', '/bower_components/diff-dom/diffDOM.js', @@ -56,8 +55,7 @@ define([ Links, nThen, SFCommon, - ApiConfig, - CommonRealtime) + ApiConfig) { var saveAs = window.saveAs; var Messages = Cryptpad.Messages; @@ -339,8 +337,6 @@ define([ } }; - CommonRealtime.onInfiniteSpinner(function () { setEditable(false); }); - // don't let the user edit until the pad is ready setEditable(false); @@ -727,6 +723,15 @@ define([ cpNfInner = common.startRealtime(realtimeOptions); metadataMgr = cpNfInner.metadataMgr; + cpNfInner.onInfiniteSpinner(function () { + setEditable(false); + Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) { + if (!yes) { return; } + common.gotoURL(); + //window.parent.location.reload(); + }); + }); + Cryptpad.onLogout(function () { setEditable(false); }); /* hitting enter makes a new line, but places the cursor inside diff --git a/www/slide/app-slide.less b/www/slide/app-slide.less index 5b0cb98be..a847f78ce 100644 --- a/www/slide/app-slide.less +++ b/www/slide/app-slide.less @@ -33,6 +33,7 @@ h6 { font-size: 24px; } .CodeMirror { height: 100%; + min-height: 100%; font-size: initial; } .CodeMirror-focused .cm-matchhighlight {