From 2e1deeb8ede8d526e9388c56ff4a49dc84097842 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 19 Aug 2021 10:44:09 +0200 Subject: [PATCH] Open static spreadsheets (xlsx, ods) in the sheet app --- www/common/common-util.js | 20 ++++++ www/common/drive-ui.js | 88 ++++++++++++++++++++------ www/common/onlyoffice/inner.js | 102 ++++++++++++++++++++++++++++-- www/common/sframe-common-outer.js | 7 +- 4 files changed, 190 insertions(+), 27 deletions(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index 22b374ad3..4ac5c4c37 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -573,6 +573,26 @@ return false; }; + // Tell if a file is spreadsheet from its metadata={title, fileType} + Util.isSpreadsheet = function (type, name) { + return (type && + (type === 'application/vnd.oasis.opendocument.spreadsheet' || + type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) + || (name && (name.endsWith('.xlsx') || name.endsWith('.ods'))); + }; + Util.isOfficeDoc = function (type, name) { + return (type && + (type === 'application/vnd.oasis.opendocument.text' || + type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) + || (name && (name.endsWith('.docx') || name.endsWith('.odt'))); + }; + Util.isPresentation = function (type, name) { + return (type && + (type === 'application/vnd.oasis.opendocument.presentation' || + type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation')) + || (name && (name.endsWith('.pptx') || name.endsWith('.odp'))); + }; + Util.isValidURL = function (str) { var pattern = new RegExp('^(https?:\\/\\/)'+ // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 1fe900e52..abbb62687 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -89,7 +89,10 @@ define([ var faShared = 'fa-shhare-alt'; var faReadOnly = 'fa-eye'; var faPreview = 'fa-eye'; - var faOpenInCode = 'cptools-code'; + var faOpenInCode = AppConfig.applicationsIcon.code; + var faOpenInSheet = AppConfig.applicationsIcon.sheet; + var faOpenInDoc = AppConfig.applicationsIcon.doc; + var faOpenInPresentation = AppConfig.applicationsIcon.presentation; var faRename = 'fa-pencil'; var faColor = 'cptools-palette'; var faTrash = 'fa-trash'; @@ -332,6 +335,9 @@ define([ }; + Messages.fc_openInSheet = "Edit in Sheet"; // XXX + Messages.fc_openInDoc = "Edit in Document"; // XXX + Messages.fc_openInPresentation = "Edit in Presentation"; // XXX var createContextMenu = function () { var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [ h('ul.dropdown-menu', { @@ -356,6 +362,18 @@ define([ 'tabindex': '-1', 'data-icon': faOpenInCode, }, Messages.fc_openInCode)), + h('li', h('a.cp-app-drive-context-openinsheet.dropdown-item', { + 'tabindex': '-1', + 'data-icon': faOpenInSheet, + }, Messages.fc_openInSheet)), + h('li', h('a.cp-app-drive-context-openindoc.dropdown-item', { + 'tabindex': '-1', + 'data-icon': faOpenInDoc, + }, Messages.fc_openInDoc)), + h('li', h('a.cp-app-drive-context-openinpresentation.dropdown-item', { + 'tabindex': '-1', + 'data-icon': faOpenInPresentation, + }, Messages.fc_openInPresentation)), h('li', h('a.cp-app-drive-context-savelocal.dropdown-item', { 'tabindex': '-1', 'data-icon': 'fa-cloud-upload', @@ -1293,6 +1311,18 @@ define([ if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) { hide.push('openincode'); } + if (!metadata || !Util.isSpreadsheet(metadata.fileType, metadata.title) + || !priv.supportsWasm) { + hide.push('openinsheet'); + } + if (!metadata || !Util.isOfficeDoc(metadata.fileType, metadata.title) + || !priv.supportsWasm) { + hide.push('openindoc'); + } + if (!metadata || !Util.isPresentation(metadata.fileType, metadata.title) + || !priv.supportsWasm) { + hide.push('openinpresentation'); + } if (metadata.channel && metadata.channel.length < 48) { hide.push('preview'); } @@ -1309,6 +1339,9 @@ define([ containsFolder = true; hide.push('openro'); hide.push('openincode'); + hide.push('openinsheet'); + hide.push('openindoc'); + hide.push('openinpresentation'); hide.push('hashtag'); //hide.push('delete'); hide.push('makeacopy'); @@ -1324,6 +1357,9 @@ define([ hide.push('savelocal'); hide.push('openro'); hide.push('openincode'); + hide.push('openinsheet'); + hide.push('openindoc'); + hide.push('openinpresentation'); hide.push('properties', 'access'); hide.push('hashtag'); hide.push('makeacopy'); @@ -1355,7 +1391,8 @@ define([ hide.push('download'); hide.push('share'); hide.push('savelocal'); - hide.push('openincode'); // can't because of race condition + //hide.push('openincode'); // can't because of race condition + //hide.push('openinsheet'); // can't because of race condition hide.push('makeacopy'); hide.push('preview'); } @@ -1380,11 +1417,12 @@ define([ break; case 'tree': show = ['open', 'openro', 'preview', 'openincode', 'expandall', 'collapseall', - 'color', 'download', 'share', 'savelocal', 'rename', 'delete', 'makeacopy', + 'color', 'download', 'share', 'savelocal', 'rename', 'delete', + 'makeacopy', 'openinsheet', 'openindoc', 'openinpresentation', 'deleteowned', 'removesf', 'access', 'properties', 'hashtag']; break; case 'default': - show = ['open', 'openro', 'preview', 'openincode', 'share', 'download', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy', 'savelocal', 'rename']; + show = ['open', 'openro', 'preview', 'openincode', 'openinsheet', 'openindoc', 'openinpresentation', 'share', 'download', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy', 'savelocal', 'rename']; break; case 'trashtree': { show = ['empty']; @@ -4349,6 +4387,21 @@ define([ }); }; + var openInApp = function (paths, app) { + var p = paths[0]; + var el = manager.find(p.path); + 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, + fileType: _metadata.fileType, + password: _metadata.password, + channel: _metadata.channel, + }; + openIn(app, path, APP.team, _simpleData); + }; $contextMenu.on("click", "a", function(e) { e.stopPropagation(); @@ -4436,20 +4489,19 @@ define([ } else if ($this.hasClass('cp-app-drive-context-openincode')) { if (paths.length !== 1) { return; } - var p = paths[0]; - el = manager.find(p.path); - (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); - })(); + openInApp(paths, 'code'); + } + else if ($this.hasClass('cp-app-drive-context-openinsheet')) { + if (paths.length !== 1) { return; } + openInApp(paths, 'sheet'); + } + else if ($this.hasClass('cp-app-drive-context-openindoc')) { + if (paths.length !== 1) { return; } + openInApp(paths, 'doc'); + } + else if ($this.hasClass('cp-app-drive-context-openinpresentation')) { + if (paths.length !== 1) { return; } + openInApp(paths, 'presentation'); } else if ($this.hasClass('cp-app-drive-context-expandall') || $this.hasClass('cp-app-drive-context-collapseall')) { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index eb2139b96..8bd200570 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1331,8 +1331,8 @@ define([ var x2tConvertData = function (data, fileName, format, cb) { var sframeChan = common.getSframeChannel(); var e = getEditor(); - var fonts = e.FontLoader.fontInfos; - var files = e.FontLoader.fontFiles.map(function (f) { + var fonts = e && e.FontLoader.fontInfos; + var files = e && e.FontLoader.fontFiles.map(function (f) { return { 'Id': f.Id, }; }); var type = common.getMetadataMgr().getPrivateData().ooType; @@ -1341,7 +1341,7 @@ define([ type: type, fileName: fileName, outputFormat: format, - images: window.frames[0].AscCommon.g_oDocumentUrls.urls || {}, + images: (e && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {}, fonts: fonts, fonts_files: files, mediasSources: getMediasSources(), @@ -2643,9 +2643,99 @@ define([ return; } - loadDocument(newDoc, useNewDefault); - setEditable(!readOnly); - UI.removeLoadingScreen(); + var next = function () { + loadDocument(newDoc, useNewDefault); + setEditable(!readOnly); + UI.removeLoadingScreen(); + }; + + if (privateData.isNewFile && privateData.fromFileData) { + try { + (function () { + var data = privateData.fromFileData; + + var type = data.fileType; + var title = data.title; + // Fix extension if the file was renamed + if (Util.isSpreadsheet(type) && !Util.isSpreadsheet(data.title)) { + if (type === 'application/vnd.oasis.opendocument.spreadsheet') { + data.title += '.ods'; + } + if (type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') { + data.title += '.xlsx'; + } + } + if (Util.isOfficeDoc(type) && !Util.isOfficeDoc(data.title)) { + if (type === 'application/vnd.oasis.opendocument.text') { + data.title += '.odt'; + } + if (type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { + data.title += '.docx'; + } + } + if (Util.isPresentation(type) && !Util.isPresentation(data.title)) { + if (type === 'application/vnd.oasis.opendocument.presentation') { + data.title += '.odp'; + } + if (type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') { + data.title += '.pptx'; + } + } + + var href = data.href; + var password = data.password; + var parsed = Hash.parsePadUrl(href); + var secret = Hash.getSecrets('file', parsed.hash, password); + var hexFileName = secret.channel; + var fileHost = privateData.fileHost || privateData.origin; + var src = fileHost + Hash.getBlobPathFromHex(hexFileName); + var key = secret.keys && secret.keys.cryptKey; + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function () { + if (/^4/.test('' + this.status)) { + // fallback to empty sheet + console.error(this.status); + return void next(); + } + var arrayBuffer = xhr.response; + if (arrayBuffer) { + var u8 = new Uint8Array(arrayBuffer); + FileCrypto.decrypt(u8, key, function (err, decrypted) { + if (err) { + // fallback to empty sheet + console.error(err); + return void next(); + } + var blobXlsx = decrypted.content; + new Response(blobXlsx).arrayBuffer().then(function (buffer) { + var u8Xlsx = new Uint8Array(buffer); + x2tImportData(u8Xlsx, data.title, 'bin', function (bin) { + var blob = new Blob([bin], {type: 'text/plain'}); + startOO(blob, getFileType()); + Title.updateTitle(title); + UI.removeLoadingScreen(); + }); + }); + }); + } + }; + xhr.onerror = function (err) { + // fallback to empty sheet + console.error(err); + next(); + }; + xhr.send(null); + })(); + } catch (e) { + console.error(e); + next(); + } + return; + } + + next(); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index bc1f2f725..3faefe0bb 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -574,6 +574,7 @@ define([ var edPublic, curvePublic, notifications, isTemplate; var settings = {}; var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar', 'file'].indexOf(currentPad.app) !== -1; + var isOO = ['sheet', 'doc', 'presentation'].indexOf(parsed.type) !== -1; var ooDownloadData = {}; var isDeleted = isNewFile && currentPad.hash.length > 0; @@ -634,9 +635,9 @@ define([ channel: secret.channel, enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default devMode: localStorage.CryptPad_dev === "1", - fromFileData: Cryptpad.fromFileData ? { + fromFileData: Cryptpad.fromFileData ? (isOO ? Cryptpad.fromFileData : { title: Cryptpad.fromFileData.title - } : undefined, + }) : undefined, burnAfterReading: burnAfterReading, storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined), supportsWasm: Utils.Util.supportsWasm() @@ -2016,7 +2017,7 @@ define([ return; } // if we open a new code from a file - if (Cryptpad.fromFileData) { + if (Cryptpad.fromFileData && !isOO) { Cryptpad.useFile(Cryptget, function (err) { if (err) { // TODO: better messages in case of expired, deleted, etc.?