diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index a77aa1568..bc548d8f7 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -639,6 +639,7 @@ define([ button .click(common.prepareFeedback(type)) .click(function () { + if (callback) { return void callback(); } UIElements.openTemplatePicker(common, true); }); break; @@ -2094,7 +2095,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'); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index a9b2923a9..4613e5756 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -673,6 +673,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); diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 103381f62..c265d2d2b 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); }; @@ -1296,6 +1298,13 @@ define([ } } + if (APP.template) { + getEditor().setViewModeDisconnect(); + UI.removeLoadingScreen(); + makeCheckpoint(true); + return; + } + if (APP.history) { try { getEditor().asc_setRestriction(true); @@ -1829,6 +1838,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 +1961,7 @@ define([ Title.setToolbar(toolbar); if (window.CP_DEV_MODE) { + var $save = common.createButton('save', true, {}, function () { makeCheckpoint(true); }); @@ -1866,18 +1978,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 +2071,10 @@ define([ load: loadSnapshot }); toolbar.$drawer.append($snapshot); + + // Import template + var $template = common.createButton('importtemplate', true, {}, openTemplatePicker); + $template.appendTo(toolbar.$drawer); })(); } @@ -2129,11 +2233,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 +2368,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/sframe-common-outer.js b/www/common/sframe-common-outer.js index 086841f73..6114b5d1a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1097,6 +1097,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); @@ -1277,6 +1281,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); @@ -1639,6 +1647,7 @@ 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) { @@ -1647,11 +1656,34 @@ define([ } 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/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 });