Merge branch 'staging' into trim-history

pull/1/head
ansuz 5 years ago
commit a0f1680e85

@ -7,6 +7,9 @@ www/common/highlight/
www/common/jquery-ui/
www/common/onlyoffice/sdkjs
www/common/onlyoffice/web-apps
www/common/onlyoffice/x2t
www/common/onlyoffice/v1
www/common/onlyoffice/v2
server.js
www/common/old-media-tag.js

@ -1,3 +1,58 @@
# Kouprey release (3.10.0)
## Goals
For this release we aimed to finish the last major feature of our CryptPad Teams project as well as some long-awaited features that we've planned to demo at FOSDEM 2020.
## Update notes
The CryptPad repository's _docs_ directory now includes a _systemd service file_ which you can use to ensure that CryptPad stays up and running. We're working on some step-by-step documentation to describe how to make use of it, but for now you can probably find some instructions by searching the web.
We've also updated the provided example.nginx.conf to include a minor but important change to the CSP settings for our OnlyOffice spreadsheet integration.
Up until now we have not been deleting unowned encrypted files from our server. As of this release `cryptpad/scripts/evict-inactive.js` includes logic to identify inactive, unpinned files. Identified files are first moved to your instance's _archive_ directory for a configurable period, after which they are deleted. This script is not run automatically, so if you haven't configured a cron job to run periodically then inactive files will not be removed. We recommend running the script once per day at a time when you expect your server to be relatively idle, since it consumes a non-negligible amount of server resources.
Finally, in case you live in a political jurisdiction that requires web site administrators to display their legal information, we've made it easier to add a link to a custom page. See `cryptpad/www/common/application_config_internal.js` for details, particularly the comments above `config.imprint`.
To update from v3.9.0:
1. update the CSP settings in your reverse proxy's configuration file to match those in nginx.example.conf
* don't forget to reload your server to ensure that your changes are deployed
2. stop your API server
3. pull the latest server/client code with `git pull origin master`
4. install the latest clientside dependencies with `bower update`
5. relaunch your server
## Features
* Owned pads can now be shared in _self-destruct_ mode as an additional option in the _access rights_ section of the _share menu_.
* to use self-destructing pads:
1. select `View once and self-destruct`
2. share the _self-destructing pad link_ directly with a contact or create and copy a link
3. recipients who open the link will land on a warning page informing them about what is about to happen
4. once they click through the link, they'll see the content and automatically delete it from the server
5. opening the same link a second time will not yield any content
* note that deletion affects the original document that you choose to share. It does not create a copy
* We no longer consider spreadsheets to be a BETA application!
* we've been using them for some time and while there are still points to improve we consider them stable enough for regular use
* this change in status is due to a few big updates:
1. we've integrated a recent version of OnlyOffice in which a number of bugs were fixed
2. we've enabled the use of spreadsheets for unregistered users, though registration is still free and will provide a better experience
3. it's now possible to upload encrypted images into your spreadsheets, in case you're the type of person that puts images in spreadsheets
4. you can also import and export spreadsheets between CryptPad's internal format and XLSX. This conversion is run entirely in your browser, so your documents stay private. Unfortunately it relies on some new features that are not available in all browsers. Chrome currently supports it, and we expect Firefox to enable support as of February 11th, 2020
* Finally, we've continued to receive contributions from our numerous translators (via https://weblate.cryptpad.fr) in the following languages (alphabetical order):
* Catalan
* Finnish
* German
* Italian
* Spanish
## Bug fixes
* We found and fixed an incorrect usage of the pinned-data API in `scripts/check-account-deletion.js`.
* We also updated an incorrect client-side test in /assert/.
* A minor bug in our CSS caching system caused some content to be unnecessarily recompiled. We've implemented a fix which should speed up loading time.
# JamaicanMonkey release (3.9.0)
## Goals

@ -1,7 +1,7 @@
# We use multi stage builds
FROM node:10-stretch-slim AS build
FROM node:12-stretch-slim AS build
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq git jq python
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq git jq python curl
RUN npm install -g bower
# install tini in this stage to avoid the need of jq and python
@ -16,7 +16,7 @@ RUN npm install --production \
&& npm install -g bower \
&& bower install --allow-root
FROM node:10-stretch-slim
FROM node:12-stretch-slim
# You want USE_SSL=true if not putting cryptpad behind a proxy
ENV USE_SSL=false

@ -1,9 +1,10 @@
define([
'/common/hyperscript.js',
'/common/common-language.js',
'/customize/application_config.js',
'/customize/messages.js',
'jquery',
], function (h, Language, Msg, $) {
], function (h, Language, AppConfig, Msg, $) {
var Pages = {};
Pages.setHTML = function (e, html) {
@ -58,6 +59,8 @@ define([
return h('a', attrs, text);
};
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint);
Pages.infopageFooter = function () {
return h('footer', [
h('div.container', [
@ -94,6 +97,7 @@ define([
footerCol('footer_legal', [
footLink('/terms.html', 'footer_tos'),
footLink('/privacy.html', 'privacy'),
AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined,
]),
/*footerCol('footer_contact', [
footLink('https://riot.im/app/#/room/#cryptpad:matrix.org', null, 'Chat'),
@ -103,7 +107,7 @@ define([
])*/
])
]),
h('div.cp-version-footer', "CryptPad v3.9.0 (JamaicanMonkey)")
h('div.cp-version-footer', "CryptPad v3.10.0 (Kouprey)")
]);
};

@ -439,6 +439,9 @@
i {
margin-right: 10px;
}
&.cp-alert-top {
margin-top: @alertify_padding-base;
}
&.alert-primary {
background-color: @alertify-base;
color: @alertify-fg;

@ -133,7 +133,7 @@
@colortheme_ooslide-color: #FFF;
@colortheme_ooslide-warn: #cd2532;
@colortheme_oocell-bg: #7e983f;
@colortheme_oocell-bg: #40865c;
@colortheme_oocell-color: #FFF;
@colortheme_oocell-warn: #cd2532;

@ -0,0 +1,23 @@
[Unit]
Description=CryptPad API server
[Service]
ExecStart=/home/cryptpad/.nvm/versions/node/v12.14.0/bin/node /home/cryptpad/cryptpad/server.js
# modify to match the location of your cryptpad repository
WorkingDirectory=/home/cryptpad/cryptpad
Restart=always
# Restart service after 10 seconds if node service crashes
RestartSec=2
# Output to syslog
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=cryptpad
User=cryptpad
Group=cryptpad
# modify to match your working directory
Environment='PWD="/home/cryptpad/cryptpad/cryptpad"'
[Install]
WantedBy=multi-user.target

@ -103,7 +103,7 @@ server {
# they unfortunately still require exceptions to the sandboxing to work correctly.
if ($uri = "/pad/inner.html") { set $unsafe 1; }
if ($uri = "/sheet/inner.html") { set $unsafe 1; }
if ($uri = "/common/onlyoffice/web-apps/apps/spreadsheeteditor/main/index.html") { set $unsafe 1; }
if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; }
# everything except the sandbox domain is a privileged scope, as they might be used to handle keys
if ($host != sandbox.cryptpad.info) { set $unsafe 0; }

2
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "cryptpad",
"version": "3.9.0",
"version": "3.10.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "3.9.0",
"version": "3.10.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",

@ -15,6 +15,8 @@ var inactiveTime = +new Date() - (config.inactiveTime * 24 * 3600 * 1000);
// files which were archived before this date can be considered safe to remove
var retentionTime = +new Date() - (config.archiveRetentionTime * 24 * 3600 * 1000);
var retainData = Boolean(config.retainData);
var getNewestTime = function (stats) {
return stats[['atime', 'ctime', 'mtime'].reduce(function (a, b) {
return stats[b] > stats[a]? b: a;
@ -116,6 +118,8 @@ nThen(function (w) {
store.listArchivedChannels(handler, w(done));
}).nThen(function (w) {
if (typeof(config.archiveRetentionTime) !== "number") { return; }
// Iterate over archive blob ownership proofs and remove them
// if they are older than the specified retention time
var removed = 0;
blobs.list.archived.proofs(function (err, item, next) {
if (err) {
@ -138,6 +142,8 @@ nThen(function (w) {
}));
}).nThen(function (w) {
if (typeof(config.archiveRetentionTime) !== "number") { return; }
// Iterate over archived blobs and remove them
// if they are older than the specified retention time
var removed = 0;
blobs.list.archived.blobs(function (err, item, next) {
if (err) {
@ -158,29 +164,57 @@ nThen(function (w) {
}, w(function () {
Log.info('EVICT_ARCHIVED_BLOBS_REMOVED', removed);
}));
/* TODO find a reliable metric for determining the activity of blobs...
}).nThen(function (w) {
var blobCount = 0;
var lastHour = 0;
// iterate over blobs and remove them
// if they have not been accessed within the specified retention time
var removed = 0;
blobs.list.blobs(function (err, item, next) {
blobCount++;
if (err) {
Log.error("EVICT_BLOB_LIST_BLOBS_ERROR", err);
return void next();
}
if (pins[item.blobId]) { return void next(); }
if (item && getNewestTime(item) > retentionTime) { return void next(); }
// TODO determine when to retire blobs
console.log(item);
next();
if (!retainData) {
return void blobs.remove.blob(item.blobId, function (err) {
if (err) {
Log.error("EVICT_BLOB_ERROR", {
error: err,
item: item,
});
return void next();
}
Log.info("EVICT_BLOB_INACTIVE", {
item: item,
});
removed++;
next();
});
}
blobs.archive.blob(item.blobId, function (err) {
if (err) {
Log.error("EVICT_ARCHIVE_BLOB_ERROR", {
error: err,
item: item,
});
return void next();
}
Log.info("EVICT_ARCHIVE_BLOB", {
item: item,
});
removed++;
next();
});
}, w(function () {
console.log("Listed %s blobs", blobCount);
console.log("Listed %s blobs accessed in the last hour", lastHour);
Log.info('EVICT_BLOBS_REMOVED', removed);
}));
}).nThen(function (w) {
var proofCount = 0;
// iterate over blob proofs and remove them
// if they don't correspond to a pinned or active file
var removed = 0;
blobs.list.proofs(function (err, item, next) {
proofCount++;
if (err) {
next();
return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err);
@ -205,13 +239,13 @@ nThen(function (w) {
if (err) {
return Log.error("EVICT_BLOB_PROOF_LONELY_ERROR", item);
}
removed++;
return Log.info("EVICT_BLOB_PROOF_LONELY", item);
});
});
}, function () {
console.log("Listed %s blob proofs", proofCount);
});
*/
}, w(function () {
Log.info("EVICT_BLOB_PROOFS_REMOVED", removed);
}));
}).nThen(function (w) {
var removed = 0;
var channels = 0;

@ -70,8 +70,10 @@ var setHeaders = (function () {
return function (req, res) {
const h = [
/^\/pad(2)?\/inner\.html.*/,
/^\/common\/onlyoffice\/.*\/index\.html.*/,
/^\/sheet\/inner\.html.*/,
/^\/common\/onlyoffice\/.*\/index\.html.*/
/^\/ooslide\/inner\.html.*/,
/^\/oodoc\/inner\.html.*/,
].some((regex) => {
return regex.test(req.url)
}) ? padHeaders : headers;

@ -32,6 +32,13 @@ define(function() {
*/
//config.availableLanguages = ['en', 'fr', 'de'];
/* You can display a link to the imprint (legal notice) of your website in the static pages
* footer. To do so, you can either set the following value to `true` and create an imprint.html page
* in the `customize` directory. You can also set it to an absolute URL if your imprint page already exists.
*/
config.imprint = false;
// config.imprint = true;
// config.imprint = 'https://xwiki.com/en/company/legal-notice';
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds

@ -15,7 +15,6 @@ var factory = function (Util, Crypto, Nacl) {
.decodeUTF8(JSON.stringify(list))));
};
// XXX move this code?
Hash.generateSignPair = function () {
var ed = Nacl.sign.keyPair();
var makeSafe = function (key) {
@ -152,7 +151,7 @@ Version 1
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (data.length === 86) { // XXX 88 characters - 2 trailing "="...
if (data.length === 86) {
k = data;
return true;
}

@ -55,6 +55,10 @@ define([
return $('button.ok').last();
};
UI.removeModals = function () {
$('div.alertify').remove();
};
var listenForKeys = UI.listenForKeys = function (yes, no, el) {
var handler = function (e) {
e.stopPropagation();
@ -375,6 +379,7 @@ define([
dialog.getButtons = function (buttons, onClose) {
if (!Array.isArray(buttons)) { return void console.error('Not an array'); }
if (!buttons.length) { return; }
var navs = [];
buttons.forEach(function (b) {
if (!b.name || !b.onClick) { return; }

@ -66,8 +66,14 @@ define([
$files.on('change', function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function (e) { f(e.target.result, file); };
reader.readAsText(file, type);
var parsed = file && file.name && /.+\.([^.]+)$/.exec(file.name);
var ext = parsed && parsed[1];
reader.onload = function (e) { f(e.target.result, file, ext); };
if (cfg && cfg.binary && cfg.binary.indexOf(ext) !== -1) {
reader.readAsArrayBuffer(file, type);
} else {
reader.readAsText(file, type);
}
});
};
};
@ -926,7 +932,6 @@ define([
waitFor.abort();
return;
}
console.warn('BAR');
href = url;
setTimeout(w);
});
@ -1071,12 +1076,17 @@ define([
var makeBurnAfterReadingUrl = function (common, href, channel, cb) {
var keyPair = Hash.generateSignPair();
var parsed = Hash.parsePadUrl(href);
console.error(href, parsed);
var newHref = parsed.getUrl({
ownerKey: keyPair.safeSignKey
});
var sframeChan = common.getSframeChannel();
var rtChannel;
NThen(function (waitFor) {
if (parsed.type !== "sheet") { return; }
common.getPadAttribute('rtChannel', waitFor(function (err, chan) {
rtChannel = chan;
}));
}).nThen(function (waitFor) {
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
command: 'ADD_OWNERS',
@ -1087,6 +1097,17 @@ define([
UI.warn(Messages.error);
}
}));
if (rtChannel) {
sframeChan.query('Q_SET_PAD_METADATA', {
channel: rtChannel,
command: 'ADD_OWNERS',
value: [keyPair.validateKey]
}, waitFor(function (err) {
if (err) {
console.error(err);
}
}));
}
}).nThen(function () {
cb(newHref);
});
@ -1099,7 +1120,7 @@ define([
if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }
// check if the pad is password protected
// check if the pad is password protected
var hash = hashes.editHash || hashes.viewHash;
var href = origin + pathname + '#' + hash;
var parsedHref = Hash.parsePadUrl(href);
@ -1107,7 +1128,7 @@ define([
var makeFaqLink = function () {
var link = h('span', [
h('i.fa.fa-question-circle'),
h('i.fa.fa-question-circle'),
h('a', {href: '#'}, Messages.passwordFaqLink)
]);
$(link).click(function () {
@ -1115,7 +1136,7 @@ define([
});
return link;
};
var parsed = Hash.parsePadUrl(pathname);
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
@ -1130,8 +1151,8 @@ define([
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
UI.createRadio('accessRights', 'cp-share-editable-true',
Messages.share_linkEdit, false, { mark: {tabindex:1} })]),
burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading ||
'View once and self-destruct', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX temp KEY
burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading,
false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined
]);
// Burn after reading
@ -1140,7 +1161,7 @@ define([
// the options to generate the BAR url
var barAlert = h('div.alert.alert-danger.cp-alertify-bar-selected', {
style: 'display: none;'
}, Messages.burnAfterReading_warningLink || " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server."); // XXX temp KEY
}, Messages.burnAfterReading_warningLink);
var channel = Hash.getSecrets('pad', hash, config.password).channel;
common.getPadMetadata({
channel: channel
@ -1182,7 +1203,7 @@ define([
cb(url);
});
}
return Messages.burnAfterReading_generateLink || 'Click on the button below to generate a link'; // XXX temp KEY
return Messages.burnAfterReading_generateLink;
}
var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash;
var href = burnAfterReading ? burnAfterReadingUrl : (origin + pathname + '#' + hash);
@ -1212,18 +1233,18 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
linkContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'),
h('i.fa.fa-lock'),
Messages.share_linkPasswordAlert, h('br'),
makeFaqLink()
]));
}
// warning about sharing links
// warning about sharing links
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
h('span.cp-inline-alert-text', Messages.share_linkWarning),
dismissButton
]);
@ -1303,12 +1324,11 @@ define([
onFriendShare.reg(saveValue);
// XXX Don't display access rights if no contacts
var contactsContent = h('div.cp-share-modal');
var $contactsContent = $(contactsContent);
$contactsContent.append(friendsList);
// Show alert if the pad is password protected
if (hasPassword) {
$contactsContent.append(h('div.alert.alert-primary', [
@ -1349,7 +1369,7 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
embedContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'), ' ',
h('i.fa.fa-lock'), ' ',
Messages.share_embedPasswordAlert, h('br'),
makeFaqLink()
]));
@ -1495,13 +1515,13 @@ define([
if (!hashes.fileHash) { throw new Error("You must provide a file hash"); }
var url = origin + pathname + '#' + hashes.fileHash;
// check if the file is password protected
// check if the file is password protected
var parsedHref = Hash.parsePadUrl(url);
var hasPassword = parsedHref.hashData.password;
var makeFaqLink = function () {
var link = h('span', [
h('i.fa.fa-question-circle'),
h('i.fa.fa-question-circle'),
h('a', {href: '#'}, Messages.passwordFaqLink)
]);
$(link).click(function () {
@ -1533,12 +1553,12 @@ define([
]));
}
// warning about sharing links
// warning about sharing links
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
h('span.cp-inline-alert-text', Messages.share_linkWarning),
dismissButton
]);
@ -1590,7 +1610,7 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
$contactsContent.append(h('div.alert.alert-primary', [
h('i.fa.fa-unlock'),
h('i.fa.fa-unlock'),
Messages.share_contactPasswordAlert, h('br'),
makeFaqLink()
]));
@ -1616,7 +1636,7 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
embed.append(h('div.alert.alert-primary', [
h('i.fa.fa-lock'), ' ',
h('i.fa.fa-lock'), ' ',
Messages.share_embedPasswordAlert, h('br'),
makeFaqLink()
]));
@ -1863,7 +1883,7 @@ define([
seeds: seeds,
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
waitFor.abort();
$(linkSpin).hide();
$(linkForm).show();
$nav.find('button.cp-teams-invite-create').show();
@ -1979,9 +1999,10 @@ define([
// Old import button, used in settings
button
.click(common.prepareFeedback(type))
.click(importContent('text/plain', function (content, file) {
callback(content, file);
}, {accept: data ? data.accept : undefined}));
.click(importContent((data && data.binary) ? 'application/octet-stream' : 'text/plain', callback, {
accept: data ? data.accept : undefined,
binary: data ? data.binary : undefined
}));
//}
break;
case 'upload':
@ -4056,8 +4077,8 @@ define([
};
UIElements.displayBurnAfterReadingPage = function (common, cb) {
var info = h('p.cp-password-info', Messages.burnAfterReading_warning || 'This document will self-destruct as soon as you open it. It will be removed form the server, once you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later. '); // XXX temp KEY
var button = h('button.primary', Messages.burnAfterReading_proceed || 'view and delete'); // XXX temp KEY
var info = h('p.cp-password-info', Messages.burnAfterReading_warningAccess);
var button = h('button.primary', Messages.burnAfterReading_proceed);
$(button).on('click', function () {
cb();
@ -4072,7 +4093,7 @@ define([
UIElements.getBurnAfterReadingWarning = function (common) {
var priv = common.getMetadataMgr().getPrivateData();
if (!priv.burnAfterReading) { return; }
return h('div.alert.alert-danger.cp-burn-after-reading', Messages.burnAfterReading_warningDeleted || 'This pad has been deleted from the server, once you close this window you will not be able to access it again.'); // XXX temp KEY
return h('div.alert.alert-danger.cp-burn-after-reading', Messages.burnAfterReading_warningDeleted);
};
var crowdfundingState = false;

@ -27,6 +27,16 @@ body.cp-app-sheet, body.cp-app-oodoc, body.cp-app-ooslide {
);
}
.cp-oo-x2tXls {
display: flex;
align-items: center;
justify-content: center;
.fa {
font-size: 30px;
margin-right: 10px;
}
}
#cp-fileupload {
display: none !important;
}

@ -7,6 +7,8 @@ define([
'/common/common-interface.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-ui-elements.js',
'/common/hyperscript.js',
'/api/config',
'/customize/messages.js',
'/customize/application_config.js',
@ -31,6 +33,8 @@ define([
UI,
Hash,
Util,
UIElements,
h,
ApiConfig,
Messages,
AppConfig,
@ -42,17 +46,20 @@ define([
Channel)
{
var saveAs = window.saveAs;
var Nacl = window.nacl;
var APP = window.APP = {
$: $
};
var CHECKPOINT_INTERVAL = 50;
var DISPLAY_RESTORE_BUTTON = false;
var NEW_VERSION = 2;
var debug = function (x) {
if (!window.CP_DEV_MODE) { return; }
console.log(x);
console.debug(x);
};
var stringify = function (obj) {
@ -61,6 +68,7 @@ define([
var toolbar;
var andThen = function (common) {
var Title;
var sframeChan = common.getSframeChannel();
@ -71,7 +79,9 @@ define([
var config = {};
var content = {
hashes: {},
ids: {}
ids: {},
mediasSources: {},
version: privateData.ooForceVersion ? Number(privateData.ooForceVersion) : NEW_VERSION
};
var oldHashes = {};
var oldIds = {};
@ -80,6 +90,14 @@ define([
var myOOId;
var sessionId = Hash.createChannelId();
// This structure is used for caching media data and blob urls for each media cryptpad url
var mediasData = {};
var getMediasSources = APP.getMediasSources = function() {
content.mediasSources = content.mediasSources || {};
return content.mediasSources;
};
var getId = function () {
return metadataMgr.getNetfluxId() + '-' + privateData.clientId;
};
@ -225,6 +243,9 @@ define([
var fixSheets = function () {
try {
var editor = window.frames[0].editor;
// if we are not in the sheet app
// we should not call this code
if (typeof editor.GetSheets === 'undefined') { return; }
var s = editor.GetSheets();
if (s.length === 0) { return; }
var wb = s[0].worksheet.workbook;
@ -251,13 +272,27 @@ define([
};
oldHashes = JSON.parse(JSON.stringify(content.hashes));
content.saveLock = undefined;
// If this is a migration, set the new version
if (APP.migrate) {
delete content.migration;
content.version = NEW_VERSION;
}
APP.onLocal();
APP.realtime.onSettle(function () {
fixSheets();
UI.log(Messages.saved);
if (ev.callback) {
return void ev.callback();
}
APP.realtime.onSettle(function () {
if (APP.migrate) {
UI.removeModals();
UI.alert(Messages.oo_sheetMigration_complete, function () {
common.gotoURL();
});
return;
}
if (ev.callback) {
return void ev.callback();
}
});
});
sframeChan.query('Q_OO_COMMAND', {
cmd: 'UPDATE_HASH',
@ -292,6 +327,7 @@ define([
APP.FM.handleFile(blob, data);
};
var makeCheckpoint = function (force) {
if (!common.isLoggedIn()) { return; }
var locked = content.saveLock;
if (!locked || !isUserOnline(locked) || force) {
content.saveLock = myOOId;
@ -342,7 +378,6 @@ define([
sframeChan.on('EV_OO_EVENT', function (obj) {
switch (obj.ev) {
case 'READY':
rtChannel.ready = true;
break;
case 'LEAVE':
removeClient(obj.data);
@ -473,6 +508,7 @@ define([
ooChannel.queue.forEach(function (data) {
Array.prototype.push.apply(changes, data.msg.changes);
});
ooChannel.lastHash = getLastCp().hash;
send({
type: "authChanges",
changes: changes
@ -491,8 +527,8 @@ define([
buildVersion: "5.2.6",
buildNumber: 2,
licenseType: 3,
"g_cAscSpellCheckUrl": "/spellchecker",
"settings":{"spellcheckerUrl":"/spellchecker","reconnection":{"attempts":50,"delay":2000}}
//"g_cAscSpellCheckUrl": "/spellchecker",
//"settings":{"spellcheckerUrl":"/spellchecker","reconnection":{"attempts":50,"delay":2000}}
});
// Open the document
send({
@ -636,7 +672,24 @@ define([
send({ type: "message" });
break;
case "saveChanges":
// We're sending our changes to netflux
handleChanges(obj, send);
// If we're alone, clean up the medias
var m = metadataMgr.getChannelMembers().slice().filter(function (nId) {
return nId.length === 32;
});
if (m.length === 1 && APP.loadingImage <= 0) {
try {
var docs = window.frames[0].AscCommon.g_oDocumentUrls.urls || {};
var mediasSources = getMediasSources();
Object.keys(mediasSources).forEach(function (name) {
if (!docs['media/'+name]) {
delete mediasSources[name];
}
});
APP.onLocal();
} catch (e) {}
}
break;
case "unLockDocument":
if (obj.releaseLocks && content.locks && content.locks[getId()]) {
@ -664,7 +717,7 @@ define([
var startOO = function (blob, file) {
if (APP.ooconfig) { return void console.error('already started'); }
var url = URL.createObjectURL(blob);
var lock = readOnly;// || !common.isLoggedIn();
var lock = readOnly || APP.migrate;
// Config
APP.ooconfig = {
@ -690,45 +743,413 @@ define([
"id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d",
"firstname": metadataMgr.getUserData().name || Messages.anonymous,
},
"mode": readOnly || lock ? "view" : "edit"
"mode": readOnly || lock ? "view" : "edit",
"lang": (navigator.language || navigator.userLanguage || '').slice(0,2)
},
"events": {
"onAppReady": function(/*evt*/) {
var $tb = $('iframe[name="frameEditor"]').contents().find('head');
var css = '#id-toolbar-full .toolbar-group:nth-child(2), #id-toolbar-full .separator:nth-child(3) { display: none; }' +
var css = // Old OO
'#id-toolbar-full .toolbar-group:nth-child(2), #id-toolbar-full .separator:nth-child(3) { display: none; }' +
'#fm-btn-save { display: none !important; }' +
'#panel-settings-general tr.autosave { display: none !important; }' +
'#panel-settings-general tr.coauth { display: none !important; }' +
'#header { display: none !important; }' +
'#id-toolbar-full-placeholder-btn-insertimage { display: none; }' +
'#id-toolbar-full-placeholder-btn-insertequation { display: none; }';
'#title-doc-name { display: none !important; }' +
// New OO:
'#asc-gen566 { display: none !important; }' + // Insert image from url
'section[data-tab="ins"] .separator:nth-last-child(2) { display: none !important; }' + // separator
'#slot-btn-insequation { display: none !important; }' + // Insert equation
'.toolbar .tabs .ribtab:not(.canedit) { display: none !important; }' + // Switch collaborative mode
'#app-title { display: none !important; }' + // OnlyOffice logo + doc title
'#fm-btn-info { display: none !important; }' + // Author name, doc title, etc. in "File" (menu entry)
'#panel-info { display: none !important; }' + // Same but content
'#image-button-from-url { display: none !important; }' + // Inline image settings: replace with url
'#file-menu-panel .devider { display: none !important; }' + // separator in the "File" menu
'#file-menu-panel { top: 28px !important; }' + // Position of the "File" menu
'#left-btn-spellcheck, #left-btn-about { display: none !important; }'+
'div.btn-users.dropdown-toggle { display: none; !important }';
if (readOnly) {
css += '#toolbar { display: none !important; }';
}
$('<style>').text(css).appendTo($tb);
setTimeout(function () {
$(window).trigger('resize');
});
if (UI.findOKButton().length) {
UI.findOKButton().on('focusout', function () {
window.setTimeout(function () { UI.findOKButton().focus(); });
});
}
},
"onDocumentReady": function () {
if (APP.migrate && !readOnly) {
var div = h('div.cp-oo-x2tXls', [
h('span.fa.fa-spin.fa-spinner'),
h('span', Messages.oo_sheetMigration_loading)
]);
UI.openCustomModal(UI.dialog.customModal(div, {buttons: []}));
setTimeout(function () {
makeCheckpoint(true);
}, 1000);
}
}
}
};
window.onbeforeunload = function () {
var ifr = document.getElementsByTagName('iframe')[0];
if (ifr) { ifr.remove(); }
};
common.initFilePicker({
onSelect: function (data) {
if (data.type !== 'file') {
debug("Unexpected data type picked " + data.type);
return;
}
var name = data.name;
// Add image to the list
var mediasSources = getMediasSources();
mediasSources[name] = data;
APP.onLocal();
APP.realtime.onSettle(function () {
APP.getImageURL(name, function(url) {
debug("CRYPTPAD success add " + name);
APP.AddImageSuccessCallback({
name: name,
url: url
});
});
});
}
});
APP.AddImage = function(cb1, cb2) {
APP.AddImageSuccessCallback = cb1;
APP.AddImageErrorCallback = cb2;
common.openFilePicker({
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
}
});
};
APP.loadingImage = 0;
APP.getImageURL = function(name, callback) {
var mediasSources = getMediasSources();
var data = mediasSources[name];
if (typeof data === 'undefined') {
debug("CryptPad - could not find matching media for " + name);
return void callback("");
}
var blobUrl = (typeof mediasData[data.src] === 'undefined') ? "" : mediasData[data.src].src;
if (blobUrl) {
debug("CryptPad Image already loaded " + blobUrl);
return void callback(blobUrl);
}
APP.loadingImage++;
Util.fetch(data.src, function (err, u8) {
if (err) {
APP.loadingImage--;
console.error(err);
return void callback("");
}
try {
debug("Decrypt with key " + data.key);
FileCrypto.decrypt(u8, Nacl.util.decodeBase64(data.key), function (err, res) {
APP.loadingImage--;
if (err || !res.content) {
debug("Decrypting failed");
return void callback("");
}
try {
var blobUrl = URL.createObjectURL(res.content);
// store media blobUrl and content for cache and export
var mediaData = { blobUrl : blobUrl, content : "" };
mediasData[data.src] = mediaData;
var reader = new FileReader();
reader.onloadend = function () {
debug("MediaData set");
mediaData.content = reader.result;
};
reader.readAsArrayBuffer(res.content);
debug("Adding CryptPad Image " + data.name + ": " + blobUrl);
window.frames[0].AscCommon.g_oDocumentUrls.addImageUrl(data.name, blobUrl);
callback(blobUrl);
} catch (e) {}
});
} catch (e) {
APP.loadingImage--;
debug("Exception decrypting image " + data.name);
console.error(e);
callback("");
}
});
};
APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder", APP.ooconfig);
ooLoaded = true;
makeChannel();
};
var x2tInitialized = false;
var x2tInit = function(x2t) {
debug("x2t mount");
// x2t.FS.mount(x2t.MEMFS, {} , '/');
x2t.FS.mkdir('/working');
x2t.FS.mkdir('/working/media');
x2tInitialized = true;
debug("x2t mount done");
};
/*
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 x2tConvertDataInternal = function(x2t, data, fileName, outputFormat) {
debug("Converting Data for " + fileName + " to " + outputFormat);
// 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 params = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<TaskQueueDataConvert xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+ "<m_sFileFrom>/working/" + fileName + "</m_sFileFrom>"
+ "<m_sFileTo>/working/" + fileName + "." + outputFormat + "</m_sFileTo>"
+ "<m_bIsNoBase64>false</m_bIsNoBase64>"
+ "</TaskQueueDataConvert>";
// 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;
};
var x2tSaveAndConvertDataInternal = function(x2t, data, filename, extension, finalFilename) {
var type = common.getMetadataMgr().getPrivateData().ooType;
var xlsData;
if (type === "sheet" && extension !== 'xlsx') {
xlsData = x2tConvertDataInternal(x2t, data, filename, 'xlsx');
filename += '.xlsx';
} else if (type === "ooslide" && extension !== "pptx") {
xlsData = x2tConvertDataInternal(x2t, data, filename, 'pptx');
filename += '.pptx';
} else if (type === "oodoc" && 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, finalFilename) {
// Perform the x2t conversion
require(['/common/onlyoffice/x2t/x2t.js'], function() {
var x2t = window.Module;
x2t.run();
if (x2tInitialized) {
debug("x2t runtime already initialized");
x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalFilename);
}
x2t.onRuntimeInitialized = function() {
debug("x2t in runtime initialized");
// Init x2t js module
x2tInit(x2t);
x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalFilename);
};
});
};
var exportFile = function() {
var exportXLSXFile = function() {
var text = getContent();
var suggestion = Title.suggestTitle(Title.defaultTitle);
UI.prompt(Messages.exportPrompt,
Util.fixFileName(suggestion) + '.bin', function (filename) {
var ext = ['.xlsx', /*'.ods',*/ '.bin'];
var type = common.getMetadataMgr().getPrivateData().ooType;
var warning = '';
if (type==="ooslide") {
ext = ['.pptx', /*'.odp',*/ '.bin'];
} else if (type==="oodoc") {
ext = ['.docx', /*'.odt',*/ '.bin'];
}
if (typeof(Atomics) === "undefined") {
ext = ['.bin'];
warning = '<div class="alert alert-info cp-alert-top">'+Messages.oo_exportChrome+'</div>';
}
var types = ext.map(function (val) {
return {
tag: 'a',
attributes: {
'data-value': val,
href: '#'
},
content: val
};
});
var dropdownConfig = {
text: ext[0], // Button initial text
caretDown: true,
options: types, // Entries displayed in the menu
isSelect: true,
initialValue: ext[0],
common: common
};
var $select = UIElements.createDropdown(dropdownConfig);
UI.prompt(Messages.exportPrompt+warning, Util.fixFileName(suggestion), function (filename) {
// $select.getValue()
if (!(typeof(filename) === 'string' && filename)) { return; }
var blob = new Blob([text], {type: "application/bin;charset=utf-8"});
saveAs(blob, filename);
var ext = ($select.getValue() || '').slice(1);
if (ext === 'bin') {
var blob = new Blob([text], {type: "application/bin;charset=utf-8"});
saveAs(blob, filename+'.bin');
return;
}
var content = h('div.cp-oo-x2tXls', [
h('span.fa.fa-spin.fa-spinner'),
h('span', Messages.oo_exportInProgress)
]);
UI.openCustomModal(UI.dialog.customModal(content, {buttons: []}));
setTimeout(function () {
x2tSaveAndConvertData(text, "filename.bin", ext, filename+'.'+ext);
}, 100);
}, {
typeInput: $select[0]
}, true);
};
var x2tImportImagesInternal = function(x2t, images, i, callback) {
if (i >= images.length) {
callback();
} else {
debug("Import image " + i);
var handleFileData = {
name: images[i],
mediasSources: getMediasSources(),
callback: function() {
debug("next image");
x2tImportImagesInternal(x2t, 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");
debug("Buffer");
debug(fileData.buffer);
var blob = new Blob([fileData.buffer], {type: 'image/png'});
blob.name = images[i];
APP.FMImages.handleFile(blob, handleFileData);
}
};
var x2tImportImages = function (x2t, callback) {
if (!APP.FMImages) {
var fmConfigImages = {
noHandlers: true,
noStore: true,
body: $('body'),
onUploaded: function (ev, data) {
if (!ev.callback) { return; }
debug("Image uploaded at " + data.url);
var parsed = Hash.parsePadUrl(data.url);
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);
debug("Final src: " + src);
ev.mediasSources[ev.name] = { name : ev.name, src : src, key : key };
}
ev.callback();
}
};
APP.FMImages = common.createFileManager(fmConfigImages);
}
// 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("Sync media sources elements");
debug(getMediasSources());
APP.onLocal();
debug("Import Images finalized");
callback();
});
};
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")) {
convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "xlsx");
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);
});
};
@ -738,15 +1159,21 @@ define([
return nId.length === 32;
});
if (m.length > 1) {
UI.removeModals();
return void UI.alert(Messages.oo_cantUpload);
}
if (!content) {
UI.removeModals();
return void UI.alert(Messages.oo_invalidFormat);
   }
var blob = new Blob([content], {type: 'plain/text'});
var file = getFileType();
blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
var uploadedCallback = function() {
UI.removeModals();
UI.confirm(Messages.oo_uploaded, function (yes) {
try {
window.frames[0].editor.setViewModeDisconnect();
window.frames[0].editor.setViewModeDisconnect();
} catch (e) {}
if (!yes) { return; }
common.gotoURL();
@ -760,6 +1187,44 @@ define([
APP.FM.handleFile(blob, data);
};
var importXLSXFile = function(content, filename, ext) {
// Perform the x2t conversion
debug("Filename");
debug(filename);
if (ext === "bin") {
return void importFile(content);
}
if (typeof(Atomics) === "undefined") {
return void UI.alert(Messages.oo_invalidFormat);
}
var div = h('div.cp-oo-x2tXls', [
h('span.fa.fa-spin.fa-spinner'),
h('span', Messages.oo_importInProgress)
]);
UI.openCustomModal(UI.dialog.customModal(div, {buttons: []}));
setTimeout(function () {
require(['/common/onlyoffice/x2t/x2t.js'], function() {
var x2t = window.Module;
x2t.run();
if (x2tInitialized) {
debug("x2t runtime already initialized");
x2tConvertData(x2t, new Uint8Array(content), filename.name, "bin", function(convertedContent) {
importFile(convertedContent);
});
}
x2t.onRuntimeInitialized = function() {
debug("x2t in runtime initialized");
// Init x2t js module
x2tInit(x2t);
x2tConvertData(x2t, new Uint8Array(content), filename.name, "bin", function(convertedContent) {
importFile(convertedContent);
});
};
});
}, 100);
};
var loadLastDocument = function () {
var lastCp = getLastCp();
if (!lastCp) { return; }
@ -790,17 +1255,18 @@ define([
};
xhr.send(null);
};
var loadDocument = function (newPad) {
var loadDocument = function (noCp, useNewDefault) {
if (ooLoaded) { return; }
var type = common.getMetadataMgr().getPrivateData().ooType;
var file = getFileType();
if (!newPad) {
if (!noCp) {
// Load latest checkpoint
return void loadLastDocument();
}
var newText;
switch (type) {
case 'sheet' :
newText = EmptyCell();
newText = EmptyCell(useNewDefault);
break;
case 'oodoc':
newText = EmptyDoc();
@ -828,7 +1294,7 @@ define([
JSON.parse(content);
return true;
} catch (e) {
console.log("Failed to parse, rejecting patch");
debug("Failed to parse, rejecting patch");
return false;
}
}
@ -840,7 +1306,7 @@ define([
window.frames[0].editor.setViewModeDisconnect(true);
} catch (e) {}
}
console.log(state);
debug(state);
};
var stringifyInner = function () {
@ -852,6 +1318,23 @@ define([
return stringify(obj);
};
var pinImages = function () {
if (content.mediasSources) {
var toPin = Object.keys(content.mediasSources || {}).map(function (id) {
var data = content.mediasSources[id] || {};
var src = data.src;
if (!src) { return; }
// Remove trailing slash
if (src.slice(-1) === '/') {
src = src.slice(0, -1);
}
// Extract the channel id from the source href
return src.slice(src.lastIndexOf('/') + 1);
}).filter(Boolean);
sframeChan.query('EV_OO_PIN_IMAGES', toPin);
}
};
APP.getContent = function () { return content; };
APP.onLocal = config.onLocal = function () {
@ -861,6 +1344,7 @@ define([
// Update metadata
var content = stringifyInner();
APP.realtime.contentUpdate(content);
pinImages();
};
config.onInit = function (info) {
@ -911,11 +1395,15 @@ define([
}).attr('title', 'Restore last checkpoint').appendTo($rightside);
}
var $export = common.createButton('export', true, {}, exportFile);
$export.appendTo($rightside);
var $exportXLSX = common.createButton('export', true, {}, exportXLSXFile);
$exportXLSX.appendTo($rightside);
var $import = common.createButton('import', true, {}, importFile);
$import.appendTo($rightside);
var accept = [".bin", ".ods", ".xlsx"];
if (typeof(Atomics) === "undefined") {
accept = ['.bin'];
}
var $importXLSX = common.createButton('import', true, { accept: accept, binary : ["ods", "xlsx"] }, importXLSXFile);
$importXLSX.appendTo($rightside);
if (common.isLoggedIn()) {
common.createButton('hashtag', true).appendTo($rightside);
@ -927,7 +1415,7 @@ define([
});
$rightside.append($forget);
var helpMenu = common.createHelpMenu(['beta', 'oo']);
var helpMenu = APP.helpMenu = common.createHelpMenu(['beta', 'oo']);
$('#cp-app-oo-editor').prepend(common.getBurnAfterReadingWarning());
$('#cp-app-oo-editor').prepend(helpMenu.menu);
toolbar.$drawer.append(helpMenu.button);
@ -969,10 +1457,41 @@ define([
Title.updateTitle(Title.defaultTitle);
}
var version = 'v2/';
// Old version detected: use the old OO and start the migration if we can
if (privateData.ooForceVersion) {
if (privateData.ooForceVersion === "1") {
version = "v1/";
}
} else if (content && (!content.version || content.version === 1)) {
version = 'v1/';
APP.migrate = true;
// Registedred users can start the migration
if (common.isLoggedIn()) {
content.migration = true;
APP.onLocal();
} else {
var msg = h('div.alert.alert-warning.cp-burn-after-reading', Messages.oo_sheetMigration_anonymousEditor);
$(APP.helpMenu.menu).after(msg);
readOnly = true;
}
}
var s = h('script', {
type:'text/javascript',
src: '/common/onlyoffice/'+version+'web-apps/apps/api/documents/api.js'
});
$('#cp-app-oo-editor').append(s);
if (metadataMgr.getPrivateData().burnAfterReading && content && content.channel) {
sframeChan.event('EV_BURN_PAD', content.channel);
}
var useNewDefault = content.version && content.version >= 2;
openRtChannel(function () {
setMyId();
oldHashes = JSON.parse(JSON.stringify(content.hashes));
loadDocument(newDoc);
loadDocument(newDoc, useNewDefault);
initializing = false;
setEditable(!readOnly);
UI.removeLoadingScreen();
@ -986,6 +1505,7 @@ define([
});
};
var reloadPopup = false;
config.onRemote = function () {
if (initializing) { return; }
var userDoc = APP.realtime.getUserDoc();
@ -993,6 +1513,9 @@ define([
if (json.metadata) {
metadataMgr.updateMetadata(json.metadata);
}
var wasMigrating = content.migration;
content = json.content;
if (content.hashes) {
var latest = getLastCp(true);
@ -1005,6 +1528,7 @@ define([
}
oldHashes = JSON.parse(JSON.stringify(content.hashes));
}
if (content.ids) {
handleNewIds(oldIds, content.ids);
oldIds = JSON.parse(JSON.stringify(content.ids));
@ -1013,6 +1537,16 @@ define([
handleNewLocks(oldLocks, content.locks);
oldLocks = JSON.parse(JSON.stringify(content.locks));
}
if (content.migration) {
setEditable(false);
}
if (wasMigrating && !content.migration && !reloadPopup) {
reloadPopup = true;
UI.alert(Messages.oo_sheetMigration_complete, function () {
common.gotoURL();
});
}
pinImages();
};
config.onAbort = function () {

@ -38,6 +38,7 @@ define([
}).nThen(function (/*waitFor*/) {
var addData = function (obj) {
obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, '');
obj.ooForceVersion = localStorage.CryptPad_ooVersion || sessionStorage.CryptPad_ooVersion || "";
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('Q_OO_SAVE', function (data, cb) {
@ -64,8 +65,8 @@ define([
var owners, expire;
nThen(function (waitFor) {
if (Utils.rtConfig) {
owners = Utils.rtConfig.owners;
expire = Utils.rtConfig.expire;
owners = Utils.Util.find(Utils.rtConfig, ['metadata', 'owners']);
expire = Utils.Util.find(Utils.rtConfig, ['metadata', 'expire']);
return;
}
Cryptpad.getPadAttribute('owners', waitFor(function (err, res) {
@ -88,6 +89,32 @@ define([
}, cb);
});
});
sframeChan.on('EV_OO_PIN_IMAGES', function (list) {
Cryptpad.getPadAttribute('ooImages', function (err, res) {
if (err) { return; }
if (!res || !Array.isArray(res)) { res = []; }
var toPin = [];
var toUnpin = [];
res.forEach(function (id) {
if (list.indexOf(id) === -1) {
toUnpin.push(id);
}
});
list.forEach(function (id) {
if (res.indexOf(id) === -1) {
toPin.push(id);
}
});
toPin = Utils.Util.deduplicateString(toPin);
toUnpin = Utils.Util.deduplicateString(toUnpin);
Cryptpad.pinPads(toPin, function () {});
Cryptpad.unpinPads(toUnpin, function () {});
if (!toPin.length && !toUnpin.length) { return; }
Cryptpad.setPadAttribute('ooImages', list, function (err) {
if (err) { console.error(err); }
});
});
});
sframeChan.on('Q_OO_COMMAND', function (obj, cb) {
if (obj.cmd === 'SEND_MESSAGE') {
obj.data.msg = Utils.crypto.encrypt(JSON.stringify(obj.data.msg));

@ -1,6 +1,10 @@
define(function () {
return function () {
return 'XLSY;v2;3554;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';
return function (newOO) {
if (newOO) {
return 'XLSY;v2;3394;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';
}
return 'XLSY;v2;3554;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';
};
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save