diff --git a/www/common/common-util.js b/www/common/common-util.js
index bec6cb125..e70f4747e 100644
--- a/www/common/common-util.js
+++ b/www/common/common-util.js
@@ -575,6 +575,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
@@ -618,6 +638,32 @@
getColor().toString(16);
};
+ /* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
+ where window.crossOriginIsolated is false.
+
+ Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
+ isn't clear about why they're doing this, but since it's related to site-isolation
+ it seems they're trying to do vague security things.
+
+ In any case, there seems to be a workaround where you can still create them
+ by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
+
+ This seems unreliable, but it's better than not being able to export, since
+ we actively rely on postMessage between iframes and therefore can't afford
+ to opt for full isolation.
+ */
+ var supportsSharedArrayBuffers = function () {
+ try {
+ return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
+ } catch (err) {
+ console.error(err);
+ }
+ return false;
+ };
+ Util.supportsWasm = function () {
+ return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() || typeof(WebAssembly) === 'undefined');
+ };
+
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = Util;
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js
index d2f76ef8d..32a7e7111 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',
@@ -1275,7 +1293,7 @@ define([
hide.push('preview');
}
if ($element.is('.cp-border-color-sheet')) {
- hide.push('download');
+ //hide.push('download'); // XXX if we don't want to enable this feature yet
}
if ($element.is('.cp-app-drive-static')) {
hide.push('access', 'hashtag', 'properties', 'download');
@@ -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');
}
@@ -1367,6 +1404,9 @@ define([
if (!APP.loggedIn) {
hide.push('openparent');
hide.push('rename');
+ hide.push('openinsheet');
+ hide.push('openindoc');
+ hide.push('openinpresentation');
}
filter = function ($el, className) {
@@ -1380,11 +1420,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 +4390,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 +4492,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/make-backup.js b/www/common/make-backup.js
index 3f66c1310..a80663677 100644
--- a/www/common/make-backup.js
+++ b/www/common/make-backup.js
@@ -28,7 +28,7 @@ define([
return n;
};
- var transform = function (ctx, type, sjson, cb) {
+ var transform = function (ctx, type, sjson, cb, padData) {
var result = {
data: sjson,
ext: '.json',
@@ -41,11 +41,11 @@ define([
}
var path = '/' + type + '/export.js';
require([path], function (Exporter) {
- Exporter.main(json, function (data) {
- result.ext = Exporter.ext || '';
+ Exporter.main(json, function (data, _ext) {
+ result.ext = _ext || Exporter.ext || '';
result.data = data;
cb(result);
- });
+ }, null, ctx.sframeChan, padData);
}, function () {
cb(result);
});
@@ -117,12 +117,16 @@ define([
var opts = {
password: pData.password
};
+ var padData = {
+ hash: parsed.hash,
+ password: pData.password
+ };
var handler = ctx.sframeChan.on("EV_CRYPTGET_PROGRESS", function (data) {
if (data.hash !== parsed.hash) { return; }
updateProgress.progress(data.progress);
if (data.progress === 1) {
handler.stop();
- updateProgress.progress2(1);
+ updateProgress.progress2(2);
}
});
ctx.get({
@@ -136,14 +140,15 @@ define([
if (cancelled) { return; }
if (!res.data) { return; }
var dl = function () {
- saveAs(res.data, Util.fixFileName(name));
+ saveAs(res.data, Util.fixFileName(name)+(res.ext || ''));
};
+ updateProgress.progress2(1);
cb(null, {
metadata: res.metadata,
content: res.data,
download: dl
});
- });
+ }, padData);
});
return {
cancel: cancel
@@ -195,9 +200,16 @@ define([
});
};
+ var timeout = 60000;
+ // OO pads can only be converted one at a time so we have to give them a
+ // bigger timeout value in case there are 5 of them in the current queue
+ if (['sheet', 'doc', 'presentation'].indexOf(parsed.type) !== -1) {
+ timeout = 180000;
+ }
+
to = setTimeout(function () {
error('TIMEOUT');
- }, 60000);
+ }, timeout);
setTimeout(function () {
if (ctx.stop) { return; }
@@ -228,6 +240,9 @@ define([
zip.file(fileName, res.data, opts);
console.log('DONE ---- ' + fileName);
setTimeout(done, 500);
+ }, {
+ hash: parsed.hash,
+ password: fData.password
});
});
};
@@ -292,7 +307,7 @@ define([
};
// Main function. Create the empty zip and fill it starting from drive.root
- var create = function (data, getPad, fileHost, cb, progress, cache) {
+ var create = function (data, getPad, fileHost, cb, progress, cache, sframeChan) {
if (!data || !data.uo || !data.uo.drive) { return void cb('EEMPTY'); }
var sem = Saferphore.create(5);
var ctx = {
@@ -307,7 +322,8 @@ define([
updateProgress: progress,
max: 0,
done: 0,
- cache: cache
+ cache: cache,
+ sframeChan: sframeChan
};
var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData;
progress('reading', -1); // Msg.settings_export_reading
@@ -358,7 +374,7 @@ define([
else if (state === "done") {
updateProgress.folderProgress(3);
}
- }, ctx.cache);
+ }, ctx.cache, ctx.sframeChan);
};
var createExportUI = function (origin) {
diff --git a/www/common/onlyoffice/history.js b/www/common/onlyoffice/history.js
index b8ae4b77d..c0126023c 100644
--- a/www/common/onlyoffice/history.js
+++ b/www/common/onlyoffice/history.js
@@ -119,7 +119,7 @@ define([
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
- var initialCp = cpIndex === sortedCp.length;
+ var initialCp = cpIndex === sortedCp.length || !cp.hash;
var messages = (data.messages || []).slice(initialCp ? 0 : 1);
diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js
index 0b1ddbde8..7bbfc7d0d 100644
--- a/www/common/onlyoffice/inner.js
+++ b/www/common/onlyoffice/inner.js
@@ -20,6 +20,7 @@ define([
'/common/onlyoffice/oodoc_base.js',
'/common/onlyoffice/ooslide_base.js',
'/common/outer/worker-channel.js',
+ '/common/outer/x2t.js',
'/bower_components/file-saver/FileSaver.min.js',
@@ -47,7 +48,8 @@ define([
EmptyCell,
EmptyDoc,
EmptySlide,
- Channel)
+ Channel,
+ X2T)
{
var saveAs = window.saveAs;
var Nacl = window.nacl;
@@ -60,7 +62,7 @@ define([
var DISPLAY_RESTORE_BUTTON = false;
var NEW_VERSION = 4;
var PENDING_TIMEOUT = 30000;
- var CURRENT_VERSION = 'v4';
+ var CURRENT_VERSION = X2T.CURRENT_VERSION;
//var READONLY_REFRESH_TO = 15000;
var debug = function (x, type) {
@@ -72,34 +74,6 @@ define([
return JSONSortify(obj);
};
- /* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
- where window.crossOriginIsolated is false.
-
- Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
- isn't clear about why they're doing this, but since it's related to site-isolation
- it seems they're trying to do vague security things.
-
- In any case, there seems to be a workaround where you can still create them
- by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
-
- This seems unreliable, but it's better than not being able to export, since
- we actively rely on postMessage between iframes and therefore can't afford
- to opt for full isolation.
- */
- var supportsSharedArrayBuffers = function () {
- try {
- return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
- } catch (err) {
- console.error(err);
- }
- return false;
- };
-
- var supportsXLSX = function () {
- return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() /* || typeof (SharedArrayBuffer) === "undefined" */ || typeof(WebAssembly) === 'undefined');
- };
-
-
var toolbar;
var cursor;
@@ -136,6 +110,10 @@ define([
var startOO = function () {};
+ var supportsXLSX = function () {
+ return privateData.supportsWasm;
+ };
+
var getMediasSources = APP.getMediasSources = function() {
content.mediasSources = content.mediasSources || {};
return content.mediasSources;
@@ -256,6 +234,10 @@ define([
var getFileType = function () {
var type = common.getMetadataMgr().getPrivateData().ooType;
var title = common.getMetadataMgr().getMetadataLazy().title;
+ if (APP.downloadType) {
+ type = APP.downloadType;
+ title = "download";
+ }
var file = {};
switch(type) {
case 'doc':
@@ -499,10 +481,10 @@ define([
startOO(blob, type, true);
};
- var saveToServer = function () {
+ var saveToServer = function (blob, title) {
if (APP.cantCheckpoint) { return; } // TOO_LARGE
var text = getContent();
- if (!text) {
+ if (!text && !blob) {
setEditable(false, true);
sframeChan.query('Q_CLEAR_CACHE_CHANNELS', [
'chainpad',
@@ -513,9 +495,9 @@ define([
});
return;
}
- var blob = new Blob([text], {type: 'plain/text'});
+ blob = blob || new Blob([text], {type: 'plain/text'});
var file = getFileType();
- blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
+ blob.name = title || (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
var data = {
hash: (APP.history || APP.template) ? ooChannel.historyLastHash : ooChannel.lastHash,
index: (APP.history || APP.template) ? ooChannel.currentIndex : ooChannel.cpIndex
@@ -729,6 +711,7 @@ define([
var cp = hashes[cpId] || {};
var minor = Number(s[1]) + 1;
+ if (APP.isDownload) { minor = undefined; }
var toHash = cp.hash || 'NONE';
var fromHash = nextCpId ? hashes[nextCpId].hash : 'NONE';
@@ -737,6 +720,7 @@ define([
channel: content.channel,
lastKnownHash: fromHash,
toHash: toHash,
+ isDownload: APP.isDownload
}, function (err, data) {
if (err) { console.error(err); return void UI.errorLoadingScreen(Messages.error); }
if (!Array.isArray(data.messages)) {
@@ -746,7 +730,7 @@ define([
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
- var initialCp = major === 0;
+ var initialCp = major === 0 || !cp.hash;
var messages = (data.messages || []).slice(initialCp ? 0 : 1, minor);
messages.forEach(function (obj) {
@@ -788,6 +772,7 @@ define([
}
var file = getFileType();
var type = common.getMetadataMgr().getPrivateData().ooType;
+ if (APP.downloadType) { type = APP.downloadType; }
var blob = loadInitDocument(type, true);
ooChannel.queue = messages;
resetData(blob, file);
@@ -1422,6 +1407,39 @@ define([
});
};
+ var x2tConvertData = function (data, fileName, format, cb) {
+ var sframeChan = common.getSframeChannel();
+ var e = getEditor();
+ 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;
+ sframeChan.query('Q_OO_CONVERT', {
+ data: data,
+ type: type,
+ fileName: fileName,
+ outputFormat: format,
+ images: (e && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {},
+ fonts: fonts,
+ fonts_files: files,
+ mediasSources: getMediasSources(),
+ mediasData: mediasData
+ }, function (err, obj) {
+ if (err || !obj || !obj.data) {
+ UI.warn(Messages.error);
+ return void cb();
+ }
+ cb(obj.data, obj.images);
+ }, {
+ raw: true
+ });
+ };
+
+ // When download a sheet from the drive, we must wait for all the images
+ // to be downloaded and decrypted before converting to xlsx
+ var downloadImages = {};
+
startOO = function (blob, file, force) {
if (APP.ooconfig && !force) { return void console.error('already started'); }
var url = URL.createObjectURL(blob);
@@ -1502,6 +1520,13 @@ define([
});
}
},
+ "onError": function () {
+ console.error(arguments);
+ if (APP.isDownload) {
+ var sframeChan = common.getSframeChannel();
+ sframeChan.event('EV_OOIFRAME_DONE', '');
+ }
+ },
"onDocumentReady": function () {
evOnSync.fire();
var onMigrateRdy = Util.mkEvent();
@@ -1581,6 +1606,25 @@ define([
}
}
+ if (APP.isDownload) {
+ var bin = getContent();
+ if (!supportsXLSX()) {
+ return void sframeChan.event('EV_OOIFRAME_DONE', bin, {raw: true});
+ }
+ nThen(function (waitFor) {
+ // wait for all the images to be loaded before converting
+ Object.keys(downloadImages).forEach(function (name) {
+ downloadImages[name].reg(waitFor());
+ });
+ }).nThen(function () {
+ x2tConvertData(bin, 'filename.bin', file.type, function (xlsData) {
+ sframeChan.event('EV_OOIFRAME_DONE', xlsData, {raw: true});
+ });
+ });
+ return;
+ }
+
+
if (isLockedModal.modal && force) {
isLockedModal.modal.closeModal();
delete isLockedModal.modal;
@@ -1720,6 +1764,7 @@ define([
var mediasSources = getMediasSources();
var data = mediasSources[name];
+ downloadImages[name] = Util.mkEvent(true);
if (typeof data === 'undefined') {
debug("CryptPad - could not find matching media for " + name);
@@ -1757,6 +1802,7 @@ define([
reader.onloadend = function () {
debug("MediaData set");
mediaData.content = reader.result;
+ downloadImages[name].fire();
};
reader.readAsArrayBuffer(res.content);
debug("Adding CryptPad Image " + data.name + ": " + blobUrl);
@@ -1778,239 +1824,52 @@ define([
makeChannel();
};
- var x2tReady = Util.mkEvent(true);
- var fetchFonts = function (x2t) {
- var path = '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/';
- var e = getEditor();
- var fonts = e.FontLoader.fontInfos;
- var files = e.FontLoader.fontFiles;
- var suffixes = {
- indexR: '',
- indexB: '_Bold',
- indexBI: '_Bold_Italic',
- indexI: '_Italic',
- };
- nThen(function (waitFor) {
- fonts.forEach(function (font) {
- // Check if the font is already loaded
- if (!font.NeedStyles) { return; }
- // Pick the variants we need (regular, bold, italic)
- ['indexR', 'indexB', 'indexI', 'indexBI'].forEach(function (k) {
- if (typeof(font[k]) !== "number" || font[k] === -1) { return; } // No matching file
- var file = files[font[k]];
-
- var name = font.Name + suffixes[k] + '.ttf';
- Util.fetch(path + file.Id, waitFor(function (err, buffer) {
- if (buffer) {
- x2t.FS.writeFile('/working/fonts/' + name, buffer);
- }
- }));
- });
- });
- }).nThen(function () {
- x2tReady.fire();
- });
- };
-
- var x2tInitialized = false;
- var x2tInit = function(x2t) {
- debug("x2t mount");
- // x2t.FS.mount(x2t.MEMFS, {} , '/');
- x2t.FS.mkdir('/working');
- x2t.FS.mkdir('/working/media');
- x2t.FS.mkdir('/working/fonts');
- x2tInitialized = true;
- fetchFonts(x2t);
- debug("x2t mount done");
- };
- var getX2T = function (cb) {
- // Perform the x2t conversion
- require(['/common/onlyoffice/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header?
- var x2t = window.Module;
- x2t.run();
- if (x2tInitialized) {
- debug("x2t runtime already initialized");
- return void x2tReady.reg(function () {
- cb(x2t);
- });
- }
-
- x2t.onRuntimeInitialized = function() {
- debug("x2t in runtime initialized");
- // Init x2t js module
- x2tInit(x2t);
- x2tReady.reg(function () {
- cb(x2t);
- });
- };
- });
- };
-
-
- /*
- Converting Data
-
- This function converts a data in a specific format to the outputformat
- The filename extension needs to represent the input format
- Example: fileName=cryptpad.bin outputFormat=xlsx
- */
- var getFormatId = function (ext) {
- // Sheets
- if (ext === 'xlsx') { return 257; }
- if (ext === 'xls') { return 258; }
- if (ext === 'ods') { return 259; }
- if (ext === 'csv') { return 260; }
- if (ext === 'pdf') { return 513; }
- return;
- };
- var getFromId = function (ext) {
- var id = getFormatId(ext);
- if (!id) { return ''; }
- return ''+id+'';
- };
- var getToId = function (ext) {
- var id = getFormatId(ext);
- if (!id) { return ''; }
- return ''+id+'';
- };
- var x2tConvertDataInternal = function(x2t, data, fileName, outputFormat) {
- debug("Converting Data for " + fileName + " to " + outputFormat);
-
- // PDF
- var pdfData = '';
- if (outputFormat === "pdf" && typeof(data) === "object" && data.bin && data.buffer) {
- // Add conversion rules
- pdfData = "false" +
- "/working/fonts/";
- // writing file to mounted working disk (in memory)
- x2t.FS.writeFile('/working/' + fileName, data.bin);
- x2t.FS.writeFile('/working/pdf.bin', data.buffer);
- } else {
- // writing file to mounted working disk (in memory)
- x2t.FS.writeFile('/working/' + fileName, data);
- }
-
- // Adding images
- Object.keys(window.frames[0].AscCommon.g_oDocumentUrls.urls || {}).forEach(function (_mediaFileName) {
- var mediaFileName = _mediaFileName.substring(6);
- var mediasSources = getMediasSources();
- var mediaSource = mediasSources[mediaFileName];
- var mediaData = mediaSource ? mediasData[mediaSource.src] : undefined;
- if (mediaData) {
- debug("Writing media data " + mediaFileName);
- debug("Data");
- var fileData = mediaData.content;
- x2t.FS.writeFile('/working/media/' + mediaFileName, new Uint8Array(fileData));
- } else {
- debug("Could not find media content for " + mediaFileName);
- }
- });
-
-
- var inputFormat = fileName.split('.').pop();
-
- var params = ""
- + ""
- + "/working/" + fileName + ""
- + "/working/" + fileName + "." + outputFormat + ""
- + pdfData
- + getFromId(inputFormat)
- + getToId(outputFormat)
- + "false"
- + "";
- // writing params file to mounted working disk (in memory)
- x2t.FS.writeFile('/working/params.xml', params);
- // running conversion
- x2t.ccall("runX2T", ["number"], ["string"], ["/working/params.xml"]);
- // reading output file from working disk (in memory)
- var result;
- try {
- result = x2t.FS.readFile('/working/' + fileName + "." + outputFormat);
- } catch (e) {
- debug("Failed reading converted file");
- UI.removeModals();
- UI.warn(Messages.error);
- return "";
- }
- return result;
- };
-
APP.printPdf = function (obj, cb) {
- getX2T(function (x2t) {
- //var e = getEditor();
- //var d = e.asc_nativePrint(undefined, undefined, 0x100 + opts.printType).ImData;
- var bin = getContent();
- var xlsData = x2tConvertDataInternal(x2t, {
- buffer: obj.data,
- bin: bin
- }, 'output.bin', 'pdf');
- if (xlsData) {
- var md = common.getMetadataMgr().getMetadataLazy();
- var type = common.getMetadataMgr().getPrivateData().ooType;
- var title = md.title || md.defaultTitle || type;
- var blob = new Blob([xlsData], {type: "application/pdf"});
- //var url = URL.createObjectURL(blob, { type: "application/pdf" });
- saveAs(blob, title+'.pdf');
- //window.open(url);
- cb({
- "type":"save",
- "status":"ok",
- //"data":url + "?disposition=inline&ooname=output.pdf"
- });
- /*
- ooChannel.send({
- "type":"documentOpen",
- "data": {
- "type":"save",
- "status":"ok",
- "data":url + "?disposition=inline&ooname=output.pdf"
- }
- });
- */
- }
+ var bin = getContent();
+ x2tConvertData({
+ buffer: obj.data,
+ bin: bin
+ }, 'output.bin', 'pdf', function (xlsData) {
+ if (!xlsData) { return; }
+ var md = common.getMetadataMgr().getMetadataLazy();
+ var type = common.getMetadataMgr().getPrivateData().ooType;
+ var title = md.title || md.defaultTitle || type;
+ var blob = new Blob([xlsData], {type: "application/pdf"});
+ saveAs(blob, title+'.pdf');
+ cb({
+ "type":"save",
+ "status":"ok",
+ });
});
};
- var x2tSaveAndConvertDataInternal = function(x2t, data, filename, extension, finalFilename) {
+ var x2tSaveAndConvertData = function(data, filename, extension, finalFilename) {
var type = common.getMetadataMgr().getPrivateData().ooType;
- var xlsData;
// PDF
if (type === "sheet" && extension === "pdf") {
var e = getEditor();
var d = e.asc_nativePrint(undefined, undefined, 0x101).ImData;
- xlsData = x2tConvertDataInternal(x2t, {
+ x2tConvertData({
buffer: d.data,
bin: data
- }, filename, extension);
- if (xlsData) {
- var _blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"});
- UI.removeModals();
- saveAs(_blob, finalFilename);
- }
+ }, filename, extension, function (res) {
+ if (res) {
+ var _blob = new Blob([res], {type: "application/bin;charset=utf-8"});
+ UI.removeModals();
+ saveAs(_blob, finalFilename);
+ }
+ });
return;
}
- if (type === "sheet" && extension !== 'xlsx') {
- xlsData = x2tConvertDataInternal(x2t, data, filename, 'xlsx');
- filename += '.xlsx';
- } else if (type === "presentation" && extension !== "pptx") {
- xlsData = x2tConvertDataInternal(x2t, data, filename, 'pptx');
- filename += '.pptx';
- } else if (type === "doc" && extension !== "docx") {
- xlsData = x2tConvertDataInternal(x2t, data, filename, 'docx');
- filename += '.docx';
- }
- xlsData = x2tConvertDataInternal(x2t, data, filename, extension);
- if (xlsData) {
- var blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"});
- UI.removeModals();
- saveAs(blob, finalFilename);
- }
- };
-
- var x2tSaveAndConvertData = function(data, filename, extension, finalName) {
- getX2T(function (x2t) {
- x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalName);
+ x2tConvertData(data, filename, extension, function (xlsData) {
+ if (xlsData) {
+ var blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"});
+ UI.removeModals();
+ saveAs(blob, finalFilename);
+ return;
+ }
+ UI.warn(Messages.error);
});
};
@@ -2083,32 +1942,29 @@ define([
$select.find('button').addClass('btn');
};
- var x2tImportImagesInternal = function(x2t, images, i, callback) {
+ var x2tImportImagesInternal = function(images, i, callback) {
if (i >= images.length) {
callback();
} else {
debug("Import image " + i);
var handleFileData = {
- name: images[i],
+ name: images[i].name,
mediasSources: getMediasSources(),
callback: function() {
debug("next image");
- x2tImportImagesInternal(x2t, images, i+1, callback);
+ x2tImportImagesInternal(images, i+1, callback);
},
};
- var filePath = "/working/media/" + images[i];
- debug("Import filename " + filePath);
- var fileData = x2t.FS.readFile("/working/media/" + images[i], { encoding : "binary" });
- debug("Importing data");
+ var fileData = images[i].data;
debug("Buffer");
debug(fileData.buffer);
var blob = new Blob([fileData.buffer], {type: 'image/png'});
- blob.name = images[i];
+ blob.name = images[i].name;
APP.FMImages.handleFile(blob, handleFileData);
}
};
- var x2tImportImages = function (x2t, callback) {
+ var x2tImportImages = function (images, callback) {
if (!APP.FMImages) {
var fmConfigImages = {
noHandlers: true,
@@ -2134,14 +1990,8 @@ define([
// Import Images
debug("Import Images");
- var files = x2t.FS.readdir("/working/media/");
- var images = [];
- files.forEach(function (file) {
- if (file !== "." && file !== "..") {
- images.push(file);
- }
- });
- x2tImportImagesInternal(x2t, images, 0, function() {
+ debug(images);
+ x2tImportImagesInternal(images, 0, function() {
debug("Sync media sources elements");
debug(getMediasSources());
APP.onLocal();
@@ -2151,26 +2001,12 @@ define([
};
- var x2tConvertData = function (x2t, data, filename, extension, callback) {
- var convertedContent;
- // Convert from ODF format:
- // first convert to Office format then to the selected extension
- if (filename.endsWith(".ods")) {
- console.log(x2t, data, filename, extension);
- convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "xlsx");
- console.log(convertedContent);
- convertedContent = x2tConvertDataInternal(x2t, convertedContent, filename + ".xlsx", extension);
- } else if (filename.endsWith(".odt")) {
- convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "docx");
- convertedContent = x2tConvertDataInternal(x2t, convertedContent, filename + ".docx", extension);
- } else if (filename.endsWith(".odp")) {
- convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "pptx");
- convertedContent = x2tConvertDataInternal(x2t, convertedContent, filename + ".pptx", extension);
- } else {
- convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, extension);
- }
- x2tImportImages(x2t, function() {
- callback(convertedContent);
+ var x2tImportData = function (data, filename, extension, callback) {
+ x2tConvertData(new Uint8Array(data), filename, extension, function (binData, images) {
+ if (!binData) { return void UI.warn(Messages.error); }
+ x2tImportImages(images, function() {
+ callback(binData);
+ });
});
};
@@ -2224,10 +2060,8 @@ define([
]);
UI.openCustomModal(UI.dialog.customModal(div, {buttons: []}));
setTimeout(function () {
- getX2T(function (x2t) {
- x2tConvertData(x2t, new Uint8Array(content), filename.name, "bin", function(c) {
- importFile(c);
- });
+ x2tImportData(new Uint8Array(content), filename.name, "bin", function(c) {
+ importFile(c);
});
}, 100);
};
@@ -2453,6 +2287,40 @@ define([
});
};
+ sframeChan.on('EV_OOIFRAME_REFRESH', function (data) {
+ // We want to get the "bin" content of a sheet from its json in order to download
+ // something useful from a non-onlyoffice app (download from drive or settings).
+ // We don't want to initialize a full pad in async-store because we only need a
+ // static version, so we can use "openVersionHash" which is based on GET_HISTORY_RANGE
+ APP.isDownload = data.downloadId;
+ APP.downloadType = data.type;
+ downloadImages = {};
+ var json = data && data.json;
+ if (!json || !json.content) {
+ return void sframeChan.event('EV_OOIFRAME_DONE', '');
+ }
+ content = json.content;
+ readOnly = true;
+ var version = (!content.version || content.version === 1) ? 'v1/' :
+ (content.version <= 3 ? 'v2b/' : CURRENT_VERSION+'/');
+ var s = h('script', {
+ type:'text/javascript',
+ src: '/common/onlyoffice/'+version+'web-apps/apps/api/documents/api.js'
+ });
+ $('#cp-app-oo-editor').append(s);
+
+ var hashes = content.hashes || {};
+ var idx = sortCpIndex(hashes);
+ var lastIndex = idx[idx.length - 1];
+
+ // We're going to open using "openVersionHash" to avoid reimplementing existing code.
+ // To do so, we're using a version corresponding to the latest checkpoint with a
+ // minor version of 0. "openVersionHash" knows that it needs to give us the latest
+ // version when "APP.isDownload" is true.
+ var sheetVersion = lastIndex + '.0';
+ openVersionHash(sheetVersion);
+ });
+
config.onInit = function (info) {
var privateData = metadataMgr.getPrivateData();
metadataMgr.setDegraded(false); // FIXME degraded moded unsupported (no cursor channel)
@@ -2766,6 +2634,7 @@ define([
readOnly = true;
}
}
+ // NOTE: don't forget to also update the version in 'EV_OOIFRAME_REFRESH'
// If the sheet is locked by an offline user, remove it
if (content && content.saveLock && !isUserOnline(content.saveLock)) {
@@ -2853,9 +2722,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'});
+ saveToServer(blob, data.title);
+ 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/onlyoffice/main.js b/www/common/onlyoffice/main.js
index b7626a589..8b79474ae 100644
--- a/www/common/onlyoffice/main.js
+++ b/www/common/onlyoffice/main.js
@@ -143,6 +143,22 @@ define([
}
sframeChan.event('EV_OO_EVENT', obj);
});
+
+ // X2T
+ var x2t;
+ var onConvert = function (obj, cb) {
+ x2t.convert(obj, cb);
+ };
+ sframeChan.on('Q_OO_CONVERT', function (obj, cb) {
+ if (x2t) { return void onConvert(obj, cb); }
+ require(['/common/outer/x2t.js'], function (X2T) {
+ x2t = X2T.start();
+ onConvert(obj, cb);
+ });
+ });
+
+
+
};
SFCommonO.start({
hash: hash,
diff --git a/www/common/onlyoffice/ooiframe.js b/www/common/onlyoffice/ooiframe.js
new file mode 100644
index 000000000..097d21e5e
--- /dev/null
+++ b/www/common/onlyoffice/ooiframe.js
@@ -0,0 +1,176 @@
+// 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',
+ '/customize/messages.js',
+], function (nThen, ApiConfig, $, RequireConfig, Messages) {
+ var requireConfig = RequireConfig();
+
+ var ready = false;
+ var currentCb;
+ var queue = [];
+
+ var create = function (config) {
+ // Loaded in load #2
+ var sframeChan;
+ var refresh = function (data, cb) {
+ if (currentCb) {
+ queue.push({data: data, cb: cb});
+ return;
+ }
+ if (!ready) {
+ ready = function () {
+ refresh(data, cb);
+ };
+ return;
+ }
+ currentCb = cb;
+ sframeChan.event('EV_OOIFRAME_REFRESH', data);
+ };
+ nThen(function (waitFor) {
+ $(waitFor());
+ }).nThen(function (waitFor) {
+ var lang = Messages._languageUsed;
+ var themeKey = 'CRYPTPAD_STORE|colortheme';
+ var req = {
+ cfg: requireConfig,
+ req: [ '/common/loading.js' ],
+ pfx: window.location.origin,
+ theme: localStorage[themeKey],
+ themeOS: localStorage[themeKey+'_default'],
+ lang: lang
+ };
+ window.rc = requireConfig;
+ window.apiconf = ApiConfig;
+ $('#sbox-oo-iframe').attr('src',
+ ApiConfig.httpSafeOrigin + '/sheet/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 = typeof(msg.data) === "object" ? msg.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-oo-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) {
+ waitFor.abort();
+ return void 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,
+ secureIframe: true,
+ supportsWasm: Utils.Util.supportsWasm()
+ };
+ 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, true);
+
+ Cryptpad.padRpc.onMetadataEvent.reg(function (data) {
+ sframeChan.event('EV_RT_METADATA', data);
+ });
+
+ sframeChan.on('EV_OOIFRAME_DONE', function (data) {
+ if (queue.length) {
+ setTimeout(function () {
+ var first = queue.shift();
+ refresh(first.data, first.cb);
+ });
+ }
+ if (!currentCb) { return; }
+ currentCb(data);
+ currentCb = undefined;
+ });
+
+ // X2T
+ var x2t;
+ var onConvert = function (obj, cb) {
+ x2t.convert(obj, cb);
+ };
+ sframeChan.on('Q_OO_CONVERT', function (obj, cb) {
+ if (x2t) { return void onConvert(obj, cb); }
+ require(['/common/outer/x2t.js'], function (X2T) {
+ x2t = X2T.start();
+ onConvert(obj, cb);
+ });
+ });
+
+ sframeChan.onReady(function () {
+ if (ready === true) { return; }
+ if (typeof ready === "function") {
+ ready();
+ }
+ ready = true;
+ });
+ });
+ });
+ return {
+ refresh: refresh
+ };
+ };
+ return {
+ create: create
+ };
+});
diff --git a/www/common/outer/x2t.js b/www/common/outer/x2t.js
new file mode 100644
index 000000000..55ce85f2c
--- /dev/null
+++ b/www/common/outer/x2t.js
@@ -0,0 +1,245 @@
+define([
+ '/api/config',
+ '/bower_components/nthen/index.js',
+ '/common/common-util.js',
+], function (ApiConfig, nThen, Util) {
+ var X2T = {};
+
+ var CURRENT_VERSION = X2T.CURRENT_VERSION = 'v4';
+ var debug = function (str) {
+ if (localStorage.CryptPad_dev !== "1") { return; }
+ console.debug(str);
+ };
+
+ X2T.start = function () {
+ var x2tReady = Util.mkEvent(true);
+ var fetchFonts = function (x2t, obj, cb) {
+ if (!obj.fonts) { return void cb(); }
+ var path = ApiConfig.httpSafeOrigin + '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/';
+ var ver = '?' + ApiConfig.requireConf.urlArgs;
+ var fonts = obj.fonts;
+ var files = obj.fonts_files;
+ var suffixes = {
+ indexR: '',
+ indexB: '_Bold',
+ indexBI: '_Bold_Italic',
+ indexI: '_Italic',
+ };
+ nThen(function (waitFor) {
+ fonts.forEach(function (font) {
+ // Check if the font is already loaded
+ if (!font.NeedStyles) { return; }
+ // Pick the variants we need (regular, bold, italic)
+ ['indexR', 'indexB', 'indexI', 'indexBI'].forEach(function (k) {
+ if (typeof(font[k]) !== "number" || font[k] === -1) { return; } // No matching file
+ var file = files[font[k]];
+
+ var name = font.Name + suffixes[k] + '.ttf';
+ Util.fetch(path + file.Id + ver, waitFor(function (err, buffer) {
+ if (buffer) {
+ x2t.FS.writeFile('/working/fonts/' + name, buffer);
+ }
+ }));
+ });
+ });
+ }).nThen(function () {
+ cb();
+ });
+ };
+ var x2tInitialized = false;
+ var x2tInit = function(x2t) {
+ debug("x2t mount");
+ // x2t.FS.mount(x2t.MEMFS, {} , '/');
+ x2t.FS.mkdir('/working');
+ x2t.FS.mkdir('/working/media');
+ x2t.FS.mkdir('/working/fonts');
+ x2tInitialized = true;
+ x2tReady.fire();
+ debug("x2t mount done");
+ };
+ var getX2T = function (cb) {
+ // Perform the x2t conversion
+ require(['/common/onlyoffice/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header?
+ var x2t = window.Module;
+ x2t.run();
+ if (x2tInitialized) {
+ debug("x2t runtime already initialized");
+ return void x2tReady.reg(function () {
+ cb(x2t);
+ });
+ }
+
+ x2t.onRuntimeInitialized = function() {
+ debug("x2t in runtime initialized");
+ // Init x2t js module
+ x2tInit(x2t);
+ x2tReady.reg(function () {
+ cb(x2t);
+ });
+ };
+ });
+ };
+
+ var getFormatId = function (ext) {
+ // Sheets
+ if (ext === 'xlsx') { return 257; }
+ if (ext === 'xls') { return 258; }
+ if (ext === 'ods') { return 259; }
+ if (ext === 'csv') { return 260; }
+ if (ext === 'pdf') { return 513; }
+ // Docs
+ if (ext === 'docx') { return 65; }
+ if (ext === 'doc') { return 66; }
+ if (ext === 'odt') { return 67; }
+ if (ext === 'txt') { return 69; }
+ if (ext === 'html') { return 70; }
+
+ // Slides
+ if (ext === 'pptx') { return 129; }
+ if (ext === 'ppt') { return 130; }
+ if (ext === 'odp') { return 131; }
+
+ return;
+ };
+ var getFromId = function (ext) {
+ var id = getFormatId(ext);
+ if (!id) { return ''; }
+ return ''+id+'';
+ };
+ var getToId = function (ext) {
+ var id = getFormatId(ext);
+ if (!id) { return ''; }
+ return ''+id+'';
+ };
+
+ var x2tConvertDataInternal = function(x2t, obj) {
+ var data = obj.data;
+ var fileName = obj.fileName;
+ var outputFormat = obj.outputFormat;
+ var images = obj.images;
+ debug("Converting Data for " + fileName + " to " + outputFormat);
+
+ // PDF
+ var pdfData = '';
+ if (outputFormat === "pdf" && typeof(data) === "object" && data.bin && data.buffer) {
+ // Add conversion rules
+ pdfData = "false" +
+ "/working/fonts/";
+ // writing file to mounted working disk (in memory)
+ x2t.FS.writeFile('/working/' + fileName, data.bin);
+ x2t.FS.writeFile('/working/pdf.bin', data.buffer);
+ } else {
+ // writing file to mounted working disk (in memory)
+ x2t.FS.writeFile('/working/' + fileName, data);
+ }
+
+ // Adding images
+ Object.keys(images || {}).forEach(function (_mediaFileName) {
+ var mediaFileName = _mediaFileName.substring(6);
+ var mediasSources = obj.mediasSources || {};
+ var mediasData = obj.mediasData || {};
+ var mediaSource = mediasSources[mediaFileName];
+ var mediaData = mediaSource ? mediasData[mediaSource.src] : undefined;
+ if (mediaData) {
+ debug("Writing media data " + mediaFileName);
+ debug("Data");
+ var fileData = mediaData.content;
+ x2t.FS.writeFile('/working/media/' + mediaFileName, new Uint8Array(fileData));
+ } else {
+ debug("Could not find media content for " + mediaFileName);
+ }
+ });
+
+
+ var inputFormat = fileName.split('.').pop();
+
+ var params = ""
+ + ""
+ + "/working/" + fileName + ""
+ + "/working/" + fileName + "." + outputFormat + ""
+ + pdfData
+ + getFromId(inputFormat)
+ + getToId(outputFormat)
+ + "false"
+ + "";
+ // writing params file to mounted working disk (in memory)
+ x2t.FS.writeFile('/working/params.xml', params);
+ // running conversion
+ x2t.ccall("runX2T", ["number"], ["string"], ["/working/params.xml"]);
+ // reading output file from working disk (in memory)
+ var result;
+ try {
+ result = x2t.FS.readFile('/working/' + fileName + "." + outputFormat);
+ } catch (e) {
+ debug("Failed reading converted file");
+ return "";
+ }
+ return result;
+ };
+
+ var convert = function (obj, cb) {
+ getX2T(function (x2t) {
+ // Fonts
+ fetchFonts(x2t, obj, function () {
+ var o = obj.outputFormat;
+
+ if (o !== 'pdf') {
+ // Add intermediary conversion to Microsoft Office format if needed
+ // (bin to pdf is allowed)
+ [
+ // Import from Open Document
+ {source: '.ods', format: 'xlsx'},
+ {source: '.odt', format: 'docx'},
+ {source: '.odp', format: 'pptx'},
+ // Export to non Microsoft Office
+ {source: '.bin', type: 'sheet', format: 'xlsx'},
+ {source: '.bin', type: 'doc', format: 'docx'},
+ {source: '.bin', type: 'presentation', format: 'pptx'},
+ ].forEach(function (_step) {
+ if (obj.fileName.endsWith(_step.source) && obj.outputFormat !== _step.format &&
+ (!_step.type || _step.type === obj.type)) {
+ obj.outputFormat = _step.format;
+ obj.data = x2tConvertDataInternal(x2t, obj);
+ obj.fileName += '.'+_step.format;
+ }
+ });
+ obj.outputFormat = o;
+ }
+
+ var data = x2tConvertDataInternal(x2t, obj);
+
+ // Convert to bin -- Import
+ // We need to extract the images
+ var images;
+ if (o === 'bin') {
+ images = [];
+ var files = x2t.FS.readdir("/working/media/");
+ files.forEach(function (file) {
+ if (file !== "." && file !== "..") {
+ var fileData = x2t.FS.readFile("/working/media/" + file, {
+ encoding : "binary"
+ });
+ images.push({
+ name: file,
+ data: fileData
+ });
+ }
+ });
+
+ }
+
+ cb({
+ data: data,
+ images: images
+ });
+ });
+ });
+ };
+
+ return {
+ convert: convert
+ };
+ };
+
+ return X2T;
+});
diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js
index 33d46caf6..e4f6c9260 100644
--- a/www/common/sframe-common-file.js
+++ b/www/common/sframe-common-file.js
@@ -660,6 +660,11 @@ define([
var updateDecryptProgress = function (progressValue) {
var text = Math.round(progressValue * 100) + '%';
text += progressValue === 1 ? '' : ' (' + Messages.download_step2 + '...)';
+ if (progressValue === 2) {
+ Messages.download_step3 = "Converting..."; // XXX
+ text = Messages.download_step3;
+ progressValue = 1;
+ }
$pv.text(text);
$pb.css({
width: (progressValue * 100) + '%'
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 5223898d1..21344cd3f 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -72,6 +72,7 @@ define([
var SFrameChannel;
var sframeChan;
var SecureIframe;
+ var OOIframe;
var Messaging;
var Notifier;
var Utils = {
@@ -98,6 +99,7 @@ define([
'/common/cryptget.js',
'/common/outer/worker-channel.js',
'/secureiframe/main.js',
+ '/common/onlyoffice/ooiframe.js',
'/common/common-messaging.js',
'/common/common-notifier.js',
'/common/common-hash.js',
@@ -112,7 +114,7 @@ define([
'/common/test.js',
'/common/userObject.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
- _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Notify,
+ _SecureIframe, _OOIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Notify,
_Constants, _Feedback, _LocalStore, _Cache, _AppConfig, _Test, _UserObject) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
@@ -120,6 +122,7 @@ define([
Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel;
SecureIframe = _SecureIframe;
+ OOIframe = _OOIframe;
Messaging = _Messaging;
Notifier = _Notifier;
Utils.Hash = _Hash;
@@ -571,6 +574,8 @@ 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;
if (isDeleted) {
@@ -631,11 +636,12 @@ 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)
+ storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined),
+ supportsWasm: Utils.Util.supportsWasm()
};
if (window.CryptPad_newSharedFolder) {
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
@@ -1032,6 +1038,51 @@ define([
}
}, cb);
});
+ sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
+ var nSecret = secret;
+ if (cfg.isDrive) {
+ // Shared folder or user hash or fs hash
+ var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash();
+ if (data.sharedFolder) { hash = data.sharedFolder.hash; }
+ if (hash) {
+ var password = (data.sharedFolder && data.sharedFolder.password) || undefined;
+ 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);
+ }
+ if (data.isDownload && ooDownloadData[data.isDownload]) {
+ var ooData = ooDownloadData[data.isDownload];
+ delete ooDownloadData[data.isDownload];
+ nSecret = Utils.Hash.getSecrets('sheet', ooData.hash, ooData.password);
+ }
+ var channel = nSecret.channel;
+ var validate = nSecret.keys.validateKey;
+ var crypto = Crypto.createEncryptor(nSecret.keys);
+ Cryptpad.getHistoryRange({
+ channel: data.channel || channel,
+ validateKey: validate,
+ toHash: data.toHash,
+ lastKnownHash: data.lastKnownHash
+ }, function (data) {
+ cb({
+ isFull: data.isFull,
+ messages: data.messages.map(function (obj) {
+ // The 3rd parameter "true" means we're going to skip signature validation.
+ // We don't need it since the message is already validated serverside by hk
+ return {
+ msg: crypto.decrypt(obj.msg, true, true),
+ serverHash: obj.serverHash,
+ author: obj.author,
+ time: obj.time
+ };
+ }),
+ lastKnownHash: data.lastKnownHash
+ });
+ });
+ });
};
addCommonRpc(sframeChan, isSafe);
@@ -1273,46 +1324,6 @@ define([
});
});
});
- sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
- var nSecret = secret;
- if (cfg.isDrive) {
- // Shared folder or user hash or fs hash
- var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash();
- if (data.sharedFolder) { hash = data.sharedFolder.hash; }
- if (hash) {
- var password = (data.sharedFolder && data.sharedFolder.password) || undefined;
- 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);
- Cryptpad.getHistoryRange({
- channel: data.channel || channel,
- validateKey: validate,
- toHash: data.toHash,
- lastKnownHash: data.lastKnownHash
- }, function (data) {
- cb({
- isFull: data.isFull,
- messages: data.messages.map(function (obj) {
- // The 3rd parameter "true" means we're going to skip signature validation.
- // We don't need it since the message is already validated serverside by hk
- return {
- msg: crypto.decrypt(obj.msg, true, true),
- serverHash: obj.serverHash,
- author: obj.author,
- time: obj.time
- };
- }),
- lastKnownHash: data.lastKnownHash
- });
- });
- });
// Store
sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) {
@@ -1462,6 +1473,38 @@ define([
initSecureModal('share', data || {});
});
+ // OO iframe
+ var OOIframeObject = {};
+ var initOOIframe = function (cfg, cb) {
+ if (!OOIframeObject.$iframe) {
+ var config = {};
+ config.addCommonRpc = addCommonRpc;
+ config.modules = {
+ Cryptpad: Cryptpad,
+ SFrameChannel: SFrameChannel,
+ Utils: Utils
+ };
+ OOIframeObject.$iframe = $('