Merge branch 'download' into cacheRT

pull/1/head
yflory 4 years ago
commit 93ddf51564

@ -1,3 +1,40 @@
# YunnanLakeNewt (3.24.0)
## Goals
We are once again working to develop some significant new features. This release is fairly small but includes some significant changes to detect and handle a variety of errors.
## Update notes
This release includes some minor corrections the recommended NGINX configuration supplied in `cryptpad/docs/example.nginx.conf`.
To update from 3.23.2 to 3.24.0:
1. Update your NGINX config to replicate the most recent changes and reload NGINX to apply them.
2. Stop the nodejs server.
3. Pull the latest code from the `3.24.0` tag or the `main` branch using `git`.
4. Ensure you have the latest clientside and serverside dependencies with `bower update` and `npm install`.
5. Restart the nodejs server.
## Features
* A variety of CryptPad's pages now feature a much-improved loading screen which provides a more informative account of what is being loaded. It also implements some generic error handling to detect and report when something has failed in a catastrophic way. This is intended to both inform users that the page is in a broken state as well as to improve the quality of the debugging information they can provide to us so that we can fix the underlying cause.
* It is now possible to create spreadsheets from templates. Template functionality has existed for a long time in our other editors, however, OnlyOffice's architecture differs significantly and required the implementation of a wholly different system.
* One user reported some confusion regarding the use of the Kanban app's _tag_ functionality. We've updated the UI to be a little more informative.
* The "table of contents" in rich text pads now includes "anchors" created via the editor's toolbar.
## Bug fixes
* Recent changes to CryptPad's recommended CSP headers enabled Firefox to export spreadsheets to XLSX format, but they also triggered some regressions due to a number of incompatible APIs.
* Our usage of the `sessionStorage` for the purpose of passing important information to editors opened in a new tab stopped working. This meant that when you created a document in a folder, the resulting new tab would not receive the argument describing where it should be stored, and would instead save it to the default location. We've addressed this by replacing our usage of sessionStorage with a new format for passing the same arguments via the hash in the new document's URL.
* The `window.print` API also failed in a variety of cases. We've updated the relevant CSP headers to only be applied on the sheet editor (to support XSLX export) but allow printing elsewhere. We've also updated some print styles to provide more appealing results.
* The table of contents available in rich text pads failed to scroll when there were a sufficient number of heading to flow beyond the length of the page. Now a scrollbar appears when necessary.
* We discovered a number of cases where the presence of an allow list prevented some valid behaviour due to the server incorrectly concluding that users were not authenticated. We've improved the client's ability to detect these cases and re-authenticate when necessary.
* We also found that when the server was under very heavy load some database queries were timing out because they were slow (but not stopped). We've addressed this to only terminate such queries if they have been entirely inactive for several minutes.
* It was possible for "safe links" to include a mode ("edit" or "view") which did not match the rights of the user opening them. For example, if a user loaded a safe link with edit rights though they only had read-only access via their "viewer" role in a team. CryptPad will now recover from such cases and open the document with the closest set of access rights that they possess.
* We found that the server query `"IS_NEW_PAD"` could return an error but that clients would incorrectly interpret such a response as a `false`. This has been corrected.
* Finally, we've modified the "trash" UI for user and team drives such that when users attempt to empty their trash of owned shared folders they are prompted to remove the items or delete them from the server entirely, as they would be with other owned assets.
# XerusDaamsi reloaded (3.23.2)
A number of instance administrators reported issues following our 3.23.1 release. We suspect the issues were caused by applying the recommended update steps out of order which would result in the incorrect HTTP header values getting cached for the most recent version of a file. Since the most recently updated headers modified some security settings, this caused a catastrophic error on clients receiving the incorrect headers which caused them to fail to load under certain circumstances.

@ -213,3 +213,33 @@ media-tag * {
width: 100%;
height: 100%;
}
media-tag button.btn {
background-color: #fff;
box-sizing: border-box;
outline: 0;
display: inline-flex;
align-items: center;
padding: 0 6px;
min-height: 36px;
line-height: 22px;
white-space: nowrap;
text-align: center;
text-transform: uppercase;
font-size: 14px;
text-decoration: none;
cursor: pointer;
border-radius: 0;
transition: none;
color: #3F4141;
border: 1px solid #3F4141;
}
media-tag button.btn:hover, media-tag button.btn:active, media-tag button.btn:focus {
background-color: #ccc;
}
media-tag button b {
margin-left: 5px;
}
media-tag button .fa {
display: inline;
margin-right: 5px;
}

@ -312,8 +312,9 @@ button.primary:hover{
return bar;
};
var hasErrored = false;
var updateLoadingProgress = function (data) {
if (!built) { return; }
if (!built || !data) { return; }
var c = types.indexOf(data.type);
if (c < current) { return console.error(data); }
try {
@ -323,18 +324,33 @@ button.primary:hover{
if (el2) { el2.innerHTML = makeList(data); }
var el3 = document.querySelector('.cp-loading-progress-container');
if (el3) { el3.innerHTML = makeBar(data); }
} catch (e) { console.error(e); }
} catch (e) {
if (!hasErrored) { console.error(e); }
}
};
window.CryptPad_updateLoadingProgress = updateLoadingProgress;
window.CryptPad_loadingError = function (err) {
if (!built) { return; }
if (err === 'Error: XDR encoding failure') {
console.warn(err);
return;
}
hasErrored = true;
var err2;
if (err === 'Script error.') {
err2 = Messages.error_unhelpfulScriptError;
}
try {
var node = document.querySelector('.cp-loading-progress');
if (!node) { return; }
if (node.parentNode) { node.parentNode.removeChild(node); }
document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;');
document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;');
document.querySelector('#cp-loading-message').innerText = err;
document.querySelector('#cp-loading-message').innerText = err2 || err;
} catch (e) { console.error(e); }
};
return function () {

@ -26,7 +26,9 @@ var getStoredLanguage = function () { return localStorage && localStorage.getIte
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; };
var getLanguage = messages._getLanguage = function () {
if (window.cryptpadLanguage) { return window.cryptpadLanguage; }
if (getStoredLanguage()) { return getStoredLanguage(); }
try {
if (getStoredLanguage()) { return getStoredLanguage(); }
} catch (e) { console.log(e); }
var l = getBrowserLanguage();
// Edge returns 'fr-FR' --> transform it to 'fr' and check again
return map[l] ? l :
@ -65,7 +67,9 @@ define(req, function(AppConfig, Default, Language) {
if (AppConfig.availableLanguages.indexOf(language) === -1) {
language = defaultLanguage;
Language = Default;
localStorage.setItem(LS_LANG, language);
try {
localStorage.setItem(LS_LANG, language);
} catch (e) { console.log(e); }
}
Object.keys(map).forEach(function (l) {
if (l === defaultLanguage) { return; }

@ -62,7 +62,7 @@ define([
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint);
Pages.versionString = "CryptPad v3.23.2 (XerusDaamsi reloaded)";
Pages.versionString = "CryptPad v3.24.0 (YunnanLakeNewt)";
// used for the about menu
Pages.imprintLink = AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined;

@ -2,6 +2,10 @@
@import (reference) "./variables.less";
.forms_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
@alertify-fore: @colortheme_modal-fg;
@alertify-btn-fg: @alertify-fore;
@alertify-light-bg: fade(@alertify-fore, 25%);
@ -124,6 +128,14 @@
font-weight: bold;
}
&.btn-default {
border-color: @cryptpad_text_col;
color: @cryptpad_text_col;
&:hover, &:active, &:focus {
background-color: #ccc;
}
}
&.danger, &.btn-danger {
background-color: @colortheme_alertify-red;
border-color: @colortheme_alertify-red-border;

@ -94,6 +94,15 @@
height: 80vh;
max-height: 90vh;
}
button.btn-default {
display: inline-flex;
.fa {
margin-right: 5px;
}
b {
margin-left: 5px;
}
}
}
media-tag:empty {
width: 100px;

@ -17,7 +17,7 @@ SyslogIdentifier=cryptpad
User=cryptpad
Group=cryptpad
# modify to match your working directory
Environment='PWD="/home/cryptpad/cryptpad/cryptpad"'
Environment='PWD="/home/cryptpad/cryptpad"'
# systemd sets the open file limit to 4000 unless you override it
# cryptpad stores its data with the filesystem, so you should increase this to match the value of `ulimit -n`

@ -167,12 +167,19 @@ var archiveDocument = function (Env, Server, cb, data) {
// Env.blobStore.archive.proof(userSafeKey, blobId, cb)
};
var restoreArchivedDocument = function (Env, Server, cb) {
// Env.msgStore.restoreArchivedChannel(channelName, cb)
// Env.blobStore.restore.blob(blobId, cb)
// Env.blobStore.restore.proof(userSafekey, blobId, cb)
var restoreArchivedDocument = function (Env, Server, cb, data) {
var id = Array.isArray(data) && data[1];
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
cb("NOT_IMPLEMENTED");
switch (id.length) {
case 32:
return void Env.msgStore.restoreArchivedChannel(id, cb);
case 48:
// Env.blobStore.restore.proof(userSafekey, id, cb) // XXX ....
return void Env.blobStore.restore.blob(id, cb);
default:
return void cb("INVALID_ID_LENGTH");
}
};
// CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_INDEX', documentID], console.log)

@ -75,21 +75,9 @@ Upload.upload = function (Env, safeKey, chunk, cb) {
Env.blobStore.upload(safeKey, chunk, cb);
};
var reportStatus = function (Env, label, safeKey, err, id) {
var data = {
safeKey: safeKey,
err: err && err.message || err,
id: id,
};
var method = err? 'error': 'info';
Env.Log[method](label, data);
};
Upload.complete = function (Env, safeKey, arg, cb) {
Env.blobStore.complete(safeKey, arg, function (err, id) {
reportStatus(Env, 'UPLOAD_COMPLETE', safeKey, err, id);
cb(err, id);
});
Env.blobStore.closeBlobstage(safeKey);
Env.completeUpload(safeKey, arg, false, cb);
};
Upload.cancel = function (Env, safeKey, arg, cb) {
@ -97,9 +85,7 @@ Upload.cancel = function (Env, safeKey, arg, cb) {
};
Upload.complete_owned = function (Env, safeKey, arg, cb) {
Env.blobStore.completeOwned(safeKey, arg, function (err, id) {
reportStatus(Env, 'UPLOAD_COMPLETE_OWNED', safeKey, err, id);
cb(err, id);
});
Env.blobStore.closeBlobstage(safeKey);
Env.completeUpload(safeKey, arg, true, cb);
};

@ -139,6 +139,15 @@ var upload = function (Env, safeKey, content, cb) {
}
};
var closeBlobstage = function (Env, safeKey) {
var session = Env.getSession(safeKey);
if (!(session && session.blobstage && typeof(session.blobstage.close) === 'function')) {
return;
}
session.blobstage.close();
delete session.blobstage;
};
// upload_cancel
var upload_cancel = function (Env, safeKey, fileSize, cb) {
var session = Env.getSession(safeKey);
@ -159,27 +168,22 @@ var upload_cancel = function (Env, safeKey, fileSize, cb) {
// upload_complete
var upload_complete = function (Env, safeKey, id, cb) {
var session = Env.getSession(safeKey);
if (session.blobstage && session.blobstage.close) {
session.blobstage.close();
delete session.blobstage;
}
closeBlobstage(Env, safeKey);
var oldPath = makeStagePath(Env, safeKey);
var newPath = makeBlobPath(Env, id);
nThen(function (w) {
// make sure the path to your final location exists
Fse.mkdirp(Path.dirname(newPath), function (e) {
Fse.mkdirp(Path.dirname(newPath), w(function (e) {
if (e) {
w.abort();
return void cb('RENAME_ERR');
}
});
}));
}).nThen(function (w) {
// make sure there's not already something in that exact location
isFile(newPath, function (e, yes) {
isFile(newPath, w(function (e, yes) {
if (e) {
w.abort();
return void cb(e);
@ -188,8 +192,8 @@ var upload_complete = function (Env, safeKey, id, cb) {
w.abort();
return void cb('RENAME_ERR');
}
cb(void 0, newPath, id);
});
cb(void 0, id);
}));
}).nThen(function () {
// finally, move the old file to the new path
// FIXME we could just move and handle the EEXISTS instead of the above block
@ -217,15 +221,7 @@ var tryId = function (path, cb) {
// owned_upload_complete
var owned_upload_complete = function (Env, safeKey, id, cb) {
var session = Env.getSession(safeKey);
// the file has already been uploaded to the staging area
// close the pending writestream
if (session.blobstage && session.blobstage.close) {
session.blobstage.close();
delete session.blobstage;
}
closeBlobstage(Env, safeKey);
if (!isValidId(id)) {
return void cb('EINVAL_ID');
}
@ -582,6 +578,9 @@ BlobStore.create = function (config, _cb) {
},
},
closeBlobstage: function (safeKey) {
closeBlobstage(Env, safeKey);
},
complete: function (safeKey, id, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }

@ -681,9 +681,9 @@ var unarchiveChannel = function (env, channelName, cb) {
// restore the metadata log
Fse.move(archiveMetadataPath, metadataPath, w(function (err) {
// if there's nothing to move, you're done.
if (err && err.code === 'ENOENT') {
/*if (err && err.code === 'ENOENT') {
return CB();
}
}*/ // XXX make sure removing this part won't break anything
// call back with an error if something goes wrong
if (err) {
w.abort();

@ -457,6 +457,38 @@ const evictInactive = function (data, cb) {
Eviction(Env, cb);
};
var reportStatus = function (Env, label, safeKey, err, id) {
var data = {
safeKey: safeKey,
err: err && err.message || err,
id: id,
};
var method = err? 'error': 'info';
Env.Log[method](label, data);
};
const completeUpload = function (data, cb) {
if (!data) { return void cb('INVALID_ARGS'); }
var owned = data.owned;
var safeKey = data.safeKey;
var arg = data.arg;
var method;
var label;
if (owned) {
method = 'completeOwned';
label = 'UPLOAD_COMPLETE_OWNED';
} else {
method = 'complete';
label = 'UPLOAD_COMPLETE';
}
Env.blobStore[method](safeKey, arg, function (err, id) {
reportStatus(Env, label, safeKey, err, id);
cb(err, id);
});
};
const COMMANDS = {
COMPUTE_INDEX: computeIndex,
COMPUTE_METADATA: computeMetadata,
@ -471,6 +503,7 @@ const COMMANDS = {
RUN_TASKS: runTasks,
WRITE_TASK: writeTask,
EVICT_INACTIVE: evictInactive,
COMPLETE_UPLOAD: completeUpload,
};
COMMANDS.INLINE = function (data, cb) {
@ -568,7 +601,7 @@ process.on('message', function (data) {
const cb = function (err, value) {
process.send({
error: err,
error: Util.serializeError(err),
txid: data.txid,
pid: data.pid,
value: value,
@ -577,7 +610,7 @@ process.on('message', function (data) {
if (!ready) {
return void init(data.config, function (err) {
if (err) { return void cb(err); }
if (err) { return void cb(Util.serializeError(err)); }
ready = true;
cb();
});

@ -9,6 +9,7 @@ const PID = process.pid;
const DB_PATH = 'lib/workers/db-worker';
const MAX_JOBS = 16;
const DEFAULT_QUERY_TIMEOUT = 60000 * 15; // increased from three to fifteen minutes because queries for very large files were taking as long as seven minutes
Workers.initialize = function (Env, config, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
@ -113,6 +114,7 @@ Workers.initialize = function (Env, config, _cb) {
const txid = guid();
var cb = Util.once(Util.mkAsync(Util.both(_cb, function (err /*, value */) {
if (err !== 'TIMEOUT') { return; }
Log.debug("WORKER_TIMEOUT_CAUSE", msg);
// in the event of a timeout the user will receive an error
// but the state used to resend a query in the event of a worker crash
// won't be cleared. This also leaks a slot that could be used to keep
@ -132,7 +134,7 @@ Workers.initialize = function (Env, config, _cb) {
state.tasks[txid] = msg;
// default to timing out affter 180s if no explicit timeout is passed
var timeout = typeof(opt.timeout) !== 'undefined'? opt.timeout: 180000;
var timeout = typeof(opt.timeout) !== 'undefined'? opt.timeout: DEFAULT_QUERY_TIMEOUT;
response.expect(txid, cb, timeout);
state.worker.send(msg);
};
@ -422,6 +424,15 @@ Workers.initialize = function (Env, config, _cb) {
}, cb);
};
Env.completeUpload = function (safeKey, arg, owned, cb) {
sendCommand({
command: "COMPLETE_UPLOAD",
owned: owned, // Boolean
safeKey: safeKey, // String (public key)
arg: arg, // String (file id)
}, cb);
};
cb(void 0);
});
};

2
package-lock.json generated

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

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

@ -136,6 +136,20 @@ app.head(/^\/common\/feedback\.html/, function (req, res, next) {
});
}());
app.use('/blob', function (req, res, next) {
if (req.method === 'HEAD') {
Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
setHeaders: function (res, path, stat) {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Headers', 'Content-Length');
res.set('Access-Control-Expose-Headers', 'Content-Length');
}
})(req, res, next);
return;
}
next();
});
app.use(function (req, res, next) {
if (req.method === 'OPTIONS' && /\/blob\//.test(req.url)) {
res.setHeader('Access-Control-Allow-Origin', '*');
@ -202,6 +216,7 @@ var serveConfig = (function () {
adminKeys: Env.admins,
inactiveTime: Env.inactiveTime,
supportMailbox: Env.supportMailbox,
defaultStorageLimit: Env.defaultStorageLimit,
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
}, null, '\t'),

@ -97,5 +97,15 @@
color: @colortheme_logo-2;
}
}
input.cp-admin-inval {
border-color: red !important;
}
.cp-admin-nopassword {
.cp-admin-pw {
display: none !important;
}
}
}

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/admin/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/admin/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -43,6 +43,8 @@ define([
'general': [
'cp-admin-flush-cache',
'cp-admin-update-limit',
'cp-admin-archive',
'cp-admin-unarchive',
// 'cp-admin-registration',
],
'quota': [
@ -107,6 +109,141 @@ define([
});
return $div;
};
Messages.admin_archiveTitle = "Archive documents"; // XXX
Messages.admin_archiveHint = "Make a document unavailable without deleting it permanently. It will be placed in an 'archive' directory and deleted after a few days (configurable in the server configuration file)."; // XXX
Messages.admin_archiveButton = "Archive";
Messages.admin_unarchiveTitle = "Restore archived documents"; // XXX
Messages.admin_unarchiveHint = "Restore a document that has previously been archived";
Messages.admin_unarchiveButton = "Restore";
Messages.admin_archiveInput = "Document URL";
Messages.admin_archiveInput2 = "Document password";
Messages.admin_archiveInval = "Invalid document";
Messages.restoredFromServer = "Pad restored";
var archiveForm = function (archive, $div, $button) {
var label = h('label', { for: 'cp-admin-archive' }, Messages.admin_archiveInput);
var input = h('input#cp-admin-archive', {
type: 'text'
});
var label2 = h('label.cp-admin-pw', {
for: 'cp-admin-archive-pw'
}, Messages.admin_archiveInput2);
var input2 = UI.passwordInput({
id: 'cp-admin-archive-pw',
placeholder: Messages.login_password
});
var $pw = $(input2);
$pw.addClass('cp-admin-pw');
var $pwInput = $pw.find('input');
$button.before(h('div.cp-admin-setlimit-form', [
label,
input,
label2,
input2
]));
$div.addClass('cp-admin-nopassword');
var parsed;
var $input = $(input).on('keypress change paste', function () {
setTimeout(function () {
$input.removeClass('cp-admin-inval');
var val = $input.val().trim();
if (!val) {
$div.toggleClass('cp-admin-nopassword', true);
return;
}
parsed = Hash.isValidHref(val);
$pwInput.val('');
if (!parsed || !parsed.hashData) {
$div.toggleClass('cp-admin-nopassword', true);
return void $input.addClass('cp-admin-inval');
}
var pw = parsed.hashData.version !== 3 && parsed.hashData.password;
$div.toggleClass('cp-admin-nopassword', !pw);
});
});
$pw.on('keypress change', function () {
setTimeout(function () {
$pw.toggleClass('cp-admin-inval', !$pwInput.val());
});
});
var clicked = false;
$button.click(function () {
if (!parsed || !parsed.hashData) {
UI.warn(Messages.admin_archiveInval);
return;
}
var pw = parsed.hashData.password ? $pwInput.val() : undefined;
var channel;
if (parsed.hashData.version === 3) {
channel = parsed.hashData.channel;
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, pw);
channel = secret && secret.channel;
}
if (!channel) {
UI.warn(Messages.admin_archiveInval);
return;
}
if (clicked) { return; }
clicked = true;
nThen(function (waitFor) {
if (!archive) { return; }
common.getFileSize(channel, waitFor(function (err, size) {
if (!err && size === 0) {
clicked = false;
waitFor.abort();
return void UI.warn(Messages.admin_archiveInval);
}
}));
}).nThen(function () {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: archive ? 'ARCHIVE_DOCUMENT' : 'RESTORE_ARCHIVED_DOCUMENT',
data: channel
}, function (err, obj) {
var e = err || (obj && obj.error);
clicked = false;
if (e) {
UI.warn(Messages.error);
console.error(e);
return;
}
UI.log(archive ? Messages.deletedFromServer : Messages.restoredFromServer);
$input.val('');
$pwInput.val('');
});
});
});
};
create['archive'] = function () {
var key = 'archive';
var $div = makeBlock(key, true);
var $button = $div.find('button');
archiveForm(true, $div, $button);
return $div;
};
create['unarchive'] = function () {
var key = 'unarchive';
var $div = makeBlock(key, true);
var $button = $div.find('button');
archiveForm(false, $div, $button);
return $div;
};
create['registration'] = function () {
var key = 'registration';
var $div = makeBlock(key, true);

@ -3,38 +3,14 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/admin/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
var addRpc = function (sframeChan, Cryptpad/*, Utils*/) {
// Adding a new avatar from the profile: pin it and store it in the object

@ -2,7 +2,7 @@
<html class="cp-app-noscroll cp-app-print">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -464,6 +464,8 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
};
if (!/^https*:\/\//.test(href)) {
// If it doesn't start with http(s), it should be a relative href
if (!/^\//.test(href)) { return ret; }
idx = href.indexOf('/#');
ret.type = href.slice(1, idx);
if (idx === -1) { return ret; }
@ -661,7 +663,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; }
}
}
return true;
return parsed;
};
Hash.decodeDataOptions = function (opts) {

@ -980,6 +980,11 @@ define([
setTimeout(cb, 750);
};
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (error === 'Error: XDR encoding failure') {
console.warn(error);
return;
}
var $loading = $('#' + LOADING);
if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen();

@ -2684,6 +2684,45 @@ define([
};
Messages.history_trimPrompt = "This document's history is very large ({0}) and it may impact the loading time. You can delete the unnecessary history.";
UIElements.displayTrimHistoryPrompt = function (common, data) {
var mb = Util.bytesToMegabytes(data.size);
var text = Messages._getKey('history_trimPrompt', [
Messages._getKey('formattedMB', [mb])
]);
var yes = h('button.cp-corner-primary', [
h('span.fa.fa-trash-o'),
Messages.trimHistory_button
]);
var no = h('button.cp-corner-cancel', Messages.crowdfunding_popup_no); // Not now
var actions = h('div', [no, yes]);
var dontShowAgain = function () {
var until = (+new Date()) + (7 * 24 * 3600 * 1000); // 7 days from now
until = (+new Date()) + 30000; // XXX 30s from now
if (data.drive) {
common.setAttribute(['drive', 'trim'], until);
return;
}
common.setPadAttribute('trim', until);
};
var modal = UI.cornerPopup(text, actions, '', {});
$(yes).click(function () {
modal.delete();
if (data.drive) {
common.openURL('/settings/#drive');
return;
}
common.getSframeChannel().event('EV_PROPERTIES_OPEN');
});
$(no).click(function () {
dontShowAgain();
modal.delete();
});
};
UIElements.displayFriendRequestModal = function (common, data) {
var msg = data.content.msg;
var userData = msg.content.user;

@ -30,6 +30,15 @@
return JSON.parse(JSON.stringify(o));
};
Util.serializeError = function (err) {
if (!(err instanceof Error)) { return err; }
var ser = {};
Object.getOwnPropertyNames(err).forEach(function (key) {
ser[key] = err[key];
});
return ser;
};
Util.tryParse = function (s) {
try { return JSON.parse(s); } catch (e) { return;}
};
@ -113,13 +122,13 @@
var handle = function (id, args) {
var fn = pending[id];
if (typeof(fn) !== 'function') {
errorHandler("MISSING_CALLBACK", {
return void errorHandler("MISSING_CALLBACK", {
id: id,
args: args,
});
}
try {
pending[id].apply(null, Array.isArray(args)? args : [args]);
fn.apply(null, Array.isArray(args)? args : [args]);
} catch (err) {
errorHandler('HANDLER_ERROR', {
error: err,
@ -287,6 +296,12 @@
return void CB(void 0, new Uint8Array(xhr.response));
};
xhr.send(null);
return {
cancel: function () {
if (xhr.abort) { xhr.abort(); }
}
};
};
Util.dataURIToBlob = function (dataURI) {

@ -148,20 +148,19 @@ define([
send();
};
common.setTabHref = function (href) {
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.href = href;
window.onhashchange = ohc;
ohc({reset: true});
};
common.setTabHash = function (hash) {
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = hash;
window.onhashchange = ohc;
ohc({reset: true});
};
(function () {
var bypassHashChange = function (key) {
return function (value) {
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location[key] = value;
window.onhashchange = ohc;
ohc({reset: true});
};
};
common.setTabHref = bypassHashChange('href');
common.setTabHash = bypassHashChange('hash');
}());
// RESTRICTED
// Settings only
@ -2066,6 +2065,32 @@ define([
var userHash;
(function iOSFirefoxFix () {
/*
For some bizarre reason Firefox on iOS throws an error during the
loading process unless we call this function. Drawing these elements
to the DOM presumably causes the JS engine to wait just a little bit longer
until some APIs we need are ready. This occurs despite all this code being
run after the usual dom-ready events. This fix was discovered while trying
to log the error messages to the DOM because it's extremely difficult
to debug Firefox iOS in the usual ways. In summary, computers are terrible.
*/
try {
var style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode('#cp-logger { display: none; }'));
document.head.appendChild(style);
var logger = document.createElement('div');
logger.setAttribute('id', 'cp-logger');
document.body.appendChild(logger);
var pre = document.createElement('pre');
pre.innerText = 'x';
logger.appendChild(pre);
} catch (err) { console.error(err); }
}());
Nthen(function (waitFor) {
if (AppConfig.beforeLogin) {
AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor());

@ -668,7 +668,7 @@ define([
}
return;
}
MediaTag(el);
var mediaObject = MediaTag(el);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
@ -676,7 +676,7 @@ define([
.map(function (el) { return el.outerHTML; })
.join('');
mediaMap[mutation.target.getAttribute('src')] = list_values;
observer.disconnect();
if (mediaObject.complete) { observer.disconnect(); }
}
});
$mt.off('click dblclick preview');

@ -4171,6 +4171,17 @@ define([
data.name = Util.fixFileName(folderName);
data.folderName = Util.fixFileName(folderName) + '.zip';
var uo = manager.user.userObject;
if (sfId && manager.folders[sfId]) {
uo = manager.folders[sfId].userObject;
}
if (uo.getFilesRecursively) {
data.list = uo.getFilesRecursively(folderElement).map(function (el) {
var d = uo.getFileData(el);
return d.channel;
});
}
APP.FM.downloadFolder(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');

@ -53,9 +53,6 @@ define([
var _downloadFile = function (ctx, fData, cb, updateProgress) {
var cancelled = false;
var cancel = function () {
cancelled = true;
};
var href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref;
var parsed = Hash.parsePadUrl(href);
var hash = parsed.hash;
@ -63,10 +60,13 @@ define([
var secret = Hash.getSecrets('file', hash, fData.password);
var src = (ctx.fileHost || '') + Hash.getBlobPathFromHex(secret.channel);
var key = secret.keys && secret.keys.cryptKey;
Util.fetch(src, function (err, u8) {
var fetchObj, decryptObj;
fetchObj = Util.fetch(src, function (err, u8) {
if (cancelled) { return; }
if (err) { return void cb('E404'); }
FileCrypto.decrypt(u8, key, function (err, res) {
decryptObj = FileCrypto.decrypt(u8, key, function (err, res) {
if (cancelled) { return; }
if (err) { return void cb(err); }
if (!res.content) { return void cb('EEMPTY'); }
@ -78,8 +78,25 @@ define([
content: res.content,
download: dl
});
}, updateProgress && updateProgress.progress2);
}, updateProgress && updateProgress.progress);
}, function (data) {
if (cancelled) { return; }
if (updateProgress && updateProgress.progress2) {
updateProgress.progress2(data);
}
});
}, function (data) {
if (cancelled) { return; }
if (updateProgress && updateProgress.progress) {
updateProgress.progress(data);
}
});
var cancel = function () {
cancelled = true;
if (fetchObj && fetchObj.cancel) { fetchObj.cancel(); }
if (decryptObj && decryptObj.cancel) { decryptObj.cancel(); }
};
return {
cancel: cancel
};
@ -162,10 +179,10 @@ define([
if (ctx.stop) { return; }
if (to) { clearTimeout(to); }
//setTimeout(g, 2000);
g();
w();
ctx.done++;
ctx.updateProgress('download', {max: ctx.max, current: ctx.done});
g();
w();
};
var error = function (err) {
@ -312,13 +329,14 @@ define([
delete ctx.zip;
};
return {
stop: stop
stop: stop,
cancel: stop
};
};
var _downloadFolder = function (ctx, data, cb, updateProgress) {
create(data, ctx.get, ctx.fileHost, function (blob, errors) {
return create(data, ctx.get, ctx.fileHost, function (blob, errors) {
if (errors && errors.length) { console.error(errors); } // TODO show user errors
var dl = function () {
saveAs(blob, data.folderName);
@ -332,8 +350,11 @@ define([
if (typeof progress.current !== "number") { return; }
updateProgress.folderProgress(progress.current / progress.max);
}
else if (state === "compressing") {
updateProgress.folderProgress(2);
}
else if (state === "done") {
updateProgress.folderProgress(1);
updateProgress.folderProgress(3);
}
});
};

@ -60,7 +60,8 @@ var factory = function (Cache) {
],
pdf: {},
download: {
text: "Download"
text: "Save",
textDl: "Load attachment"
},
Plugins: {
/**
@ -111,8 +112,8 @@ var factory = function (Cache) {
},
download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-success');
btn.innerHTML = cfg.download.text + '<br>' +
btn.setAttribute('class', 'btn btn-default');
btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' +
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
btn.addEventListener('click', function () {
saveFile(content, url, metadata.name);
@ -122,9 +123,95 @@ var factory = function (Cache) {
}
};
var makeProgressBar = function (cfg, mediaObject) {
// XXX CSP: we'll need to add style in cryptpad's less
var style = (function(){/*
.mediatag-progress-container {
position: relative;
border: 1px solid #0087FF;
background: white;
height: 25px;
display: inline-flex;
width: 200px;
align-items: center;
justify-content: center;
box-sizing: border-box;
vertical-align: top;
}
.mediatag-progress-bar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: #0087FF;
width: 0%;
}
.mediatag-progress-text {
height: 25px;
margin-left: 5px;
line-height: 25px;
vertical-align: top;
width: auto;
display: inline-block;
color: #3F4141;
font-weight: bold;
}
*/}).toString().slice(14, -3);
var container = document.createElement('div');
container.classList.add('mediatag-progress-container');
var bar = document.createElement('div');
bar.classList.add('mediatag-progress-bar');
container.appendChild(bar);
var text = document.createElement('span');
text.classList.add('mediatag-progress-text');
text.innerText = '0%';
mediaObject.on('progress', function (obj) {
var percent = obj.progress;
text.innerText = (Math.round(percent*10))/10+'%';
bar.setAttribute('style', 'width:'+percent+'%;');
});
mediaObject.tag.innerHTML = '<style>'+style+'</style>';
mediaObject.tag.appendChild(container);
mediaObject.tag.appendChild(text);
};
var makeDownloadButton = function (cfg, mediaObject, size, cb) {
var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-default');
btn.innerHTML = '<i class="fa fa-paperclip"></i>' +
cfg.download.textDl + ' <b>(' + size + 'MB)</b>';
btn.addEventListener('click', function () {
makeProgressBar(cfg, mediaObject);
cb();
});
mediaObject.tag.innerHTML = '';
mediaObject.tag.appendChild(btn);
};
var getFileSize = function (src, _cb) {
var cb = function (e, res) {
_cb(e, res);
cb = function () {};
};
// XXX Cache
var xhr = new XMLHttpRequest();
xhr.open("HEAD", src);
xhr.onerror = function () { return void cb("XHR_ERROR"); };
xhr.onreadystatechange = function() {
if (this.readyState === this.DONE) {
cb(null, Number(xhr.getResponseHeader("Content-Length")));
}
};
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
};
xhr.send();
};
// Download a blob from href
var download = function (src, _cb) {
var download = function (src, _cb, progressCb) {
var cb = function (e, res) {
_cb(e, res);
cb = function () {};
@ -140,6 +227,16 @@ var factory = function (Cache) {
xhr.open('GET', src, true);
xhr.responseType = 'arraybuffer';
var progress = function (offset) {
progressCb(offset * 100);
};
xhr.addEventListener("progress", function (evt) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
progress(percentComplete);
}
}, false);
xhr.onerror = function () { return void cb("XHR_ERROR"); };
xhr.onload = function () {
// Error?
@ -164,7 +261,6 @@ var factory = function (Cache) {
Cache.getBlobCache(cacheKey, function (err, u8) {
if (err || !u8) { return void fetch(); }
console.error('using cache', cacheKey);
cb(null, u8);
});
@ -443,6 +539,7 @@ var factory = function (Cache) {
// End media-tag rendering: display the tag and emit the event
var end = function (decrypted) {
mediaObject.complete = true;
process(mediaObject, decrypted, cfg, function (err) {
if (err) { return void emit('error', err); }
mediaObject._blob = decrypted;
@ -451,32 +548,54 @@ var factory = function (Cache) {
};
// If we have the blob in our cache, don't download & decrypt it again, just display
// XXX Store in the cache the pending mediaobject: make sure we don't download and decrypt twice the same element at the same time
if (cache[uid]) {
end(cache[uid]);
return mediaObject;
}
// Download the encrypted blob
download(src, function (err, u8Encrypted) {
var dl = function () {
// Download the encrypted blob
download(src, function (err, u8Encrypted) {
if (err) {
if (err === "XHR_ERROR 404") {
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
}
return void emit('error', err);
}
// Decrypt the blob
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
if (errDecryption) {
return void emit('error', errDecryption);
}
// Cache and display the decrypted blob
cache[uid] = u8Decrypted;
end(u8Decrypted);
}, function (progress) {
emit('progress', {
progress: 50+0.5*progress
});
});
}, function (progress) {
emit('progress', {
progress: 0.5*progress
});
});
};
if (cfg.force) { dl(); return mediaObject; }
var maxSize = 5 * 1024 * 1024;
getFileSize(src, function (err, size) {
if (err) {
if (err === "XHR_ERROR 404") {
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
}
return void emit('error', err);
}
// Decrypt the blob
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
if (errDecryption) {
return void emit('error', errDecryption);
}
// Cache and display the decrypted blob
cache[uid] = u8Decrypted;
end(u8Decrypted);
}, function (progress) {
emit('progress', {
progress: progress
});
});
if (!size || size < maxSize) { return void dl(); }
var sizeMb = Math.round(10 * size / 1024 / 1024) / 10;
makeDownloadButton(cfg, mediaObject, sizeMb, dl);
});
return mediaObject;

@ -16,6 +16,8 @@ define(['/api/config'], function (ApiConfig) {
var getPermission = Module.getPermission = function (f) {
f = f || function () {};
// "Notification.requestPermission is not a function" on Firefox 68.11.0esr
if (!Notification || typeof(Notification.requestPermission) !== 'function') { return void f(false); }
Notification.requestPermission(function (permission) {
if (permission === "granted") { f(true); }
else { f(false); }

@ -1310,6 +1310,16 @@ define([
if (APP.migrate && !readOnly) {
onMigrateRdy.fire();
}
// Check if history can/should be trimmed
var cp = getLastCp();
if (cp && cp.file && cp.hash) {
var channels = [{
channel: content.channel,
lastKnownHash: cp.hash
}];
common.checkTrimHistory(channels);
}
}
}
};
@ -2070,7 +2080,9 @@ define([
// Import template
var $template = common.createButton('importtemplate', true, {}, openTemplatePicker);
$template.appendTo(toolbar.$drawer);
if ($template && typeof($template.appendTo) === 'function') {
$template.appendTo(toolbar.$drawer);
}
})();
}

@ -3,54 +3,23 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/common-hash.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, Hash, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, Hash, SFCommonO) {
// Loaded in load #2
var hash, href, version;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
var obj = SFCommonO.initIframe(waitFor, true, true);
href = obj.href;
hash = obj.hash;
var parsed = Hash.parsePadUrl(href);
if (parsed && parsed.hashData) {
var opts = parsed.getOptions();
version = opts.versionHash;
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + '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 = 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 addData = function (obj) {
obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, '');

@ -297,6 +297,7 @@ proxy.mailboxes = {
msg: msg,
hash: hash
};
var notify = box.ready;
Handlers.add(ctx, box, message, function (dismissed, toDismiss) {
if (toDismiss) { // List of other messages to remove
dismiss(ctx, toDismiss, '', function () {
@ -314,8 +315,7 @@ proxy.mailboxes = {
}
box.content[hash] = msg;
showMessage(ctx, type, message, null, function (obj) {
if (!box.ready) { return; }
if (!obj || !obj.msg) { return; }
if (!obj || !obj.msg || !notify) { return; }
Notify.system(undefined, obj.msg);
});
});

@ -598,6 +598,8 @@ define([
Thumb.initPadThumbnails(common, options.thumbnail);
}
}
common.checkTrimHistory();
});
};
var onConnectionChange = function (info) {

@ -3,46 +3,16 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
var hash, href;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + '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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
hash: hash,

@ -21,6 +21,7 @@ var afterLoaded = function (req) {
window.parent.postMessage(JSON.stringify({ q: 'READY', txid: txid }), '*');
}, 1);
};
window.cryptpadLanguage = req.lang;
if (req.req) { require(req.req, ready); } else { ready(); }
var onReply = function (msg) {
var data = JSON.parse(msg.data);
@ -61,7 +62,6 @@ var afterLoaded = function (req) {
updated: lsUpdated,
store: data.localStore
};
window.cryptpadLanguage = data.language;
require(['/common/sframe-boot2.js'], function () { });
};
window.addEventListener('message', onReply);

@ -43,7 +43,7 @@ define([
return void console.log();
}
if (window.CryptPad_loadingError) {
window.CryptPad_loadingError(e);
return void window.CryptPad_loadingError(e);
}
throw e;
};

@ -47,8 +47,9 @@ define([
return 'cp-fileupload-element-' + String(Math.random()).substring(2);
};
Messages.fileTableHeader = "Downloads and uploads"; // XXX
var tableHeader = h('div.cp-fileupload-header', [
h('div.cp-fileupload-header-title', h('span', Messages.fileuploadHeader || 'Uploaded files')),
h('div.cp-fileupload-header-title', h('span', Messages.fileTableHeader)),
h('div.cp-fileupload-header-close', h('span.fa.fa-times')),
]);
@ -262,7 +263,8 @@ define([
// name
$('<td>').append($link).appendTo($tr);
// size
$('<td>').text(UIElements.prettySize(estimate)).appendTo($tr);
var size = estimate ? UIElements.prettySize(estimate) : '';
$(h('td.cp-fileupload-size')).text(size).appendTo($tr);
// progress
$('<td>', {'class': 'cp-fileupload-table-progress'}).append($progressContainer).appendTo($tr);
// cancel
@ -590,12 +592,11 @@ define([
queue.next();
};
/*
var cancelled = function () {
$row.find('.cp-fileupload-table-cancel').addClass('cancelled').html('').append(h('span.fa.fa-minus'));
queue.inProgress = false;
queue.next();
};*/
};
/**
* Update progress in the download panel, for downloading a file
@ -627,8 +628,21 @@ define([
* As updateDLProgress but for folders
* @param {number} progressValue Progression of download, between 0 and 1
*/
Messages.download_zip = "Building ZIP file..."; // XXX
Messages.download_zip_file = "File {0}/{1}"; // XXX
var updateProgress = function (progressValue) {
var text = Math.round(progressValue*100) + '%';
if (Array.isArray(data.list)) {
text = Messages._getKey('download_zip_file', [Math.round(progressValue * data.list.length), data.list.length]);
}
if (progressValue === 2) {
text = Messages.download_zip;
progressValue = 1;
}
if (progressValue === 3) {
text = "100%";
progressValue = 1;
}
$pv.text(text);
$pb.css({
width: (progressValue * 100) + '%'
@ -641,7 +655,8 @@ define([
get: common.getPad,
sframeChan: sframeChan,
};
downloadFunction(ctx, data, function (err, obj) {
var dl = downloadFunction(ctx, data, function (err, obj) {
$link.prepend($('<span>', {'class': 'fa fa-external-link'}))
.attr('href', '#')
.click(function (e) {
@ -657,19 +672,17 @@ define([
folderProgress: updateProgress,
});
/*
var $cancel = $('<span>', {'class': 'cp-fileupload-table-cancel-button fa fa-times'}).click(function () {
dl.cancel();
$cancel.remove();
$row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled);
cancelled();
});
*/
$row.find('.cp-fileupload-table-cancel')
.html('')
.append(h('span.fa.fa-minus'));
//.append($cancel);
var $cancel = $row.find('.cp-fileupload-table-cancel').html('');
if (dl && dl.cancel) {
$('<span>', {
'class': 'cp-fileupload-table-cancel-button fa fa-times'
}).click(function () {
dl.cancel();
$cancel.remove();
$row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled);
cancelled();
}).appendTo($cancel);
}
};
File.downloadFile = function (fData, cb) {

@ -2,10 +2,57 @@
define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/requireconfig.js',
'/customize/messages.js',
'jquery',
], function (nThen, ApiConfig, $) {
], function (nThen, ApiConfig, RequireConfig, Messages, $) {
var common = {};
common.initIframe = function (waitFor, isRt) {
var requireConfig = RequireConfig();
var lang = Messages._languageUsed;
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin,
lang: lang
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
var hash, href;
if (isRt) {
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + '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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
return {
hash: hash,
href: href
};
};
common.start = function (cfg) {
cfg = cfg || {};
var realtime = !cfg.noRealtime;
@ -76,6 +123,7 @@ define([
Utils.LocalStore = _LocalStore;
Utils.Cache = _Cache;
Utils.UserObject = _UserObject;
Utils.currentPad = currentPad;
AppConfig = _AppConfig;
Test = _Test;
@ -523,6 +571,7 @@ define([
isPresent: parsed.hashData && parsed.hashData.present,
isEmbed: parsed.hashData && parsed.hashData.embed,
isHistoryVersion: parsed.hashData && parsed.hashData.versionHash,
notifications: Notification && Notification.permission === "granted",
accounts: {
donateURL: Cryptpad.donateURL,
upgradeURL: Cryptpad.upgradeURL
@ -559,7 +608,7 @@ define([
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
if (cfg.addData) {
cfg.addData(metaObj.priv, Cryptpad, metaObj.user);
cfg.addData(metaObj.priv, Cryptpad, metaObj.user, Utils);
}
sframeChan.event('EV_METADATA_UPDATE', metaObj);
@ -1523,9 +1572,13 @@ define([
});
});
if (cfg.messaging) {
Notifier.getPermission();
sframeChan.on('Q_ASK_NOTIFICATION', function (data, cb) {
Notification.requestPermission(function (s) {
cb(s === "granted");
});
});
if (cfg.messaging) {
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
Cryptpad.universal.execCommand({
type: 'messenger',

@ -264,6 +264,66 @@ define([
return teamChatChannel;
};
// When opening a pad, if were an owner check the history size and prompt for trimming if
// necessary
funcs.checkTrimHistory = function (channels, isDrive) {
channels = channels || [];
var priv = ctx.metadataMgr.getPrivateData();
var limit = 100 * 1024 * 1024; // 100MB
limit = 100 * 1024; // XXX 100KB
var owned;
nThen(function (w) {
if (isDrive) {
funcs.getAttribute(['drive', 'trim'], w(function (err, val) {
if (err || typeof(val) !== "number") { return; }
if (val < (+new Date())) { return; }
w.abort();
}));
return;
}
funcs.getPadAttribute('trim', w(function (err, val) {
if (err || typeof(val) !== "number") { return; }
if (val < (+new Date())) { return; }
w.abort();
}));
}).nThen(function (w) {
// Check ownership
// DRIVE
if (isDrive) {
if (!priv.isDriveOwned) { return void w.abort(); }
return;
}
// PAD
channels.push({ channel: priv.channel });
funcs.getPadMetadata({
channel: priv.channel
}, w(function (md) {
if (md && md.error) { return void w.abort(); }
var owners = md.owners;
owned = funcs.isOwned(owners);
if (!owned) { return void w.abort(); }
}));
}).nThen(function () {
// We're an owner: check the history size
var history = funcs.makeUniversal('history');
history.execCommand('GET_HISTORY_SIZE', {
account: isDrive,
pad: !isDrive,
channels: channels,
teamId: typeof(owned) === "number" && owned
}, function (obj) {
if (obj && obj.error) { return; } // can't get history size: abort
var bytes = obj.size;
if (!bytes || typeof(bytes) !== "number") { return; } // no history: abort
if (bytes < limit) { return; }
obj.drive = isDrive;
UIElements.displayTrimHistoryPrompt(funcs, obj);
});
});
};
var cursorChannel;
// common-ui-elements needs to be able to get the cursor channel to put it in metadata when
// importing a template

@ -990,6 +990,30 @@ MessengerUI, Messages) {
h('div.cp-notifications-empty', Messages.notifications_empty)
]);
var pads_options = [div];
var metadataMgr = config.metadataMgr;
var privateData = metadataMgr.getPrivateData();
if (!privateData.notifications) {
Messages.allowNotifications = "Allow notifications"; // XXX
var allowNotif = h('div.cp-notifications-gotoapp', h('p', Messages.allowNotifications));
pads_options.unshift(h("hr"));
pads_options.unshift(allowNotif);
var $allow = $(allowNotif).click(function () {
Common.getSframeChannel().event('Q_ASK_NOTIFICATION', null, function (e, allow) {
if (!allow) { return; }
$(allowNotif).remove();
});
});
var onChange = function () {
var privateData = metadataMgr.getPrivateData();
if (!privateData.notifications) { return; }
$allow.remove();
metadataMgr.off('change', onChange);
};
metadataMgr.onChange(onChange);
}
if (Common.isLoggedIn()) {
pads_options.unshift(h("hr"));
pads_options.unshift(openNotifsApp);

@ -1459,5 +1459,14 @@
"admin_setlimitHint": "Lege individuelle Begrenzungen für Benutzer anhand ihrer öffentlichen Schlüssel fest. Du kannst bestehende Regeln aktualisieren oder entfernen.",
"access_destroyPad": "Dokument oder Ordner endgültig zerstören",
"fm_shareFolderPassword": "Diesen Ordner mit einem Passwort schützen (optional)",
"fm_deletedFolder": "Gelöschter Ordner"
"fm_deletedFolder": "Gelöschter Ordner",
"tag_edit": "Ändern",
"tag_add": "Hinzufügen",
"loading_state_4": "Teams laden",
"loading_state_3": "Geteilte Ordner laden",
"loading_state_2": "Inhalte aktualisieren",
"loading_state_1": "Drive laden",
"loading_state_0": "Oberfläche vorbereiten",
"loading_state_5": "Dokument rekonstruieren",
"error_unhelpfulScriptError": "Skriptfehler: Siehe Konsole im Browser für Details"
}

@ -1459,5 +1459,14 @@
"history_cantRestore": "Palauttaminen epäonnistui. Yhteytesi on katkennut.",
"history_close": "Sulje",
"history_restore": "Palauta",
"share_bar": "Luo linkki"
"share_bar": "Luo linkki",
"error_unhelpfulScriptError": "Skriptivirhe: Lisätietoja selaimen kehittäjäkonsolissa",
"tag_edit": "Muokkaa",
"tag_add": "Lisää",
"loading_state_5": "Uudelleenrakenna asiakirja",
"loading_state_4": "Lataa Teams",
"loading_state_3": "Lataa jaetut kansiot",
"loading_state_2": "Päivitä sisältö",
"loading_state_1": "Lataa Drive",
"loading_state_0": "Rakenna käyttöliittymä"
}

@ -1465,5 +1465,8 @@
"loading_state_3": "Chargement des dossiers partagés",
"loading_state_2": "Mise à jour du contenu",
"loading_state_1": "Chargement du drive",
"loading_state_0": "Construction de l'interface"
"loading_state_0": "Construction de l'interface",
"tag_edit": "Modifier",
"tag_add": "Ajouter",
"error_unhelpfulScriptError": "Erreur de script : consultez la console du navigateur pour plus de détails"
}

@ -1467,5 +1467,6 @@
"loading_state_4": "Load Teams",
"loading_state_5": "Reconstruct document",
"tag_add": "Add",
"tag_edit": "Edit"
"tag_edit": "Edit",
"error_unhelpfulScriptError": "Script Error: See browser console for details"
}

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/contacts/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/contacts/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -3,38 +3,14 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/contacts/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
noRealtime: true,

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/debug/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/debug/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/drive/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/drive/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -312,6 +312,10 @@ define([
onReconnect();
});
common.onLogout(function () { setEditable(false); });
// Check if our drive history needs to be trimmed
common.checkTrimHistory(null, true);
});
};
main();

@ -3,47 +3,17 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
var hash, href;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/drive/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
}).nThen(function (/*waitFor*/) {
var afterSecrets = function (Cryptpad, Utils, secret, cb, sframeChan) {
var parsed = Utils.Hash.parsePadUrl(href);

@ -64,7 +64,49 @@
}
}
#cp-app-file-upload-form, #cp-app-file-download-form {
#cp-app-file-download-form {
padding: 0px;
margin: 0px;
position: relative;
display: block;
max-width: 90vw;
height: 150px;
width: ~"min(90vw, 600px)";
.cp-app-file-progress-container {
margin-top: 5px;
height: 40px;
font-size: 20px;
border: 1px solid @colortheme_logo-2;
background: white;
color: @cryptpad_text_col;
display: flex;
justify-content: space-between;
position: relative;
.cp-app-file-progress-dl {
border-right: 1px solid @cryptpad_text_col;
}
.cp-app-file-progress-dl, .cp-app-file-progress-dc {
width: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.cp-app-file-progress {
z-index: 1;
position: absolute;
top: 0;
left: 0;
bottom: 0;
background: @colortheme_logo-2;
}
}
.cp-app-file-progress-txt {
margin-left: 30px;
}
}
#cp-app-file-upload-form {
padding: 0px;
margin: 0px;
@ -159,4 +201,4 @@
}
}
}
}

@ -128,6 +128,11 @@ define([
metadata: undefined,
};
var cancelled = false;
var cancel = function () {
cancelled = true;
};
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
@ -168,6 +173,7 @@ define([
var chunks = [];
var again = function () {
if (cancelled) { return; }
takeChunk(function (e, plaintext) {
if (e) {
return setTimeout(function () {
@ -188,6 +194,10 @@ define([
};
again();
return {
cancel: cancel
};
};
// metadata

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/file/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/file/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }
@ -16,11 +16,7 @@
<label for="cp-app-file-upfile" class="btn btn-primary cp-app-file-block unselectable" data-localization-title="upload_choose"
data-localization="upload_choose"></label>
</div>
<div id="cp-app-file-download-form" style="display: none;">
<input type="button" name="dl" id="cp-app-file-dlfile" class="cp-app-file-input" />
<label for="cp-app-file-dlfile" class="btn btn-success cp-app-file-block unselectable" data-localization-title="download_button"><span data-localization="download_button"></span></label>
<span class="cp-app-file-block" id="cp-app-file-dlprogress"></span>
</div>
<div id="cp-app-file-download-form" style="display: none;"> </div>
<div id="cp-app-file-download-view" style="display: none;">
<media-tag id="cp-app-file-view"></media-tag>
</div>

@ -8,6 +8,7 @@ define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/file/file-crypto.js',
@ -29,6 +30,7 @@ define([
Util,
Hash,
UI,
h,
Messages,
FileCrypto,
MediaTag)
@ -47,8 +49,6 @@ define([
var $dlform = $('#cp-app-file-download-form');
var $dlview = $('#cp-app-file-download-view');
var $label = $form.find('label');
var $dllabel = $dlform.find('label span');
var $progress = $('#cp-app-file-dlprogress');
var $bar = $('.cp-toolbar-container');
var $body = $('body');
@ -88,142 +88,174 @@ define([
var toolbar = APP.toolbar = Toolbar.create(configTb);
if (!uploadMode) {
var hexFileName = secret.channel;
var src = fileHost + Hash.getBlobPathFromHex(hexFileName);
var key = secret.keys && secret.keys.cryptKey;
var cryptKey = Nacl.util.encodeBase64(key);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') {
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () {
common.gotoURL('/file/');
});
(function () {
Messages.download = "Download"; // XXX
Messages.decrypt = "Decrypt"; // XXX
var progress = h('div.cp-app-file-progress');
var progressTxt = h('span.cp-app-file-progress-txt');
var $progress = $(progress);
var $progressTxt = $(progressTxt);
var downloadEl = h('span.cp-app-file-progress-dl', Messages.download);
var decryptEl = h('span.cp-app-file-progress-dc', Messages.decrypt);
var progressContainer = h('div.cp-app-file-progress-container', [
downloadEl,
decryptEl,
progress
]);
var hexFileName = secret.channel;
var src = fileHost + Hash.getBlobPathFromHex(hexFileName);
var key = secret.keys && secret.keys.cryptKey;
var cryptKey = Nacl.util.encodeBase64(key);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') {
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () {
common.gotoURL('/file/');
});
}
return void console.error(e);
}
return void console.error(e);
}
// Add pad attributes when the file is saved in the drive
Title.onTitleChange(function () {
var owners = metadata.owners;
if (owners) { common.setPadAttribute('owners', owners); }
common.setPadAttribute('fileType', metadata.type);
});
$(document).on('cpPadStored', function () {
var owners = metadata.owners;
if (owners) { common.setPadAttribute('owners', owners); }
common.setPadAttribute('fileType', metadata.type);
});
// Save to the drive or update the acces time
var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle);
var owners = metadata.owners;
if (owners) {
common.setPadAttribute('owners', owners);
}
if (metadata.type) {
common.setPadAttribute('fileType', metadata.type);
}
toolbar.addElement(['pageTitle'], {
pageTitle: title,
title: Title.getTitleConfig(),
});
toolbar.$drawer.append(common.createButton('forget', true));
toolbar.$drawer.append(common.createButton('properties', true));
if (common.isLoggedIn()) {
toolbar.$drawer.append(common.createButton('hashtag', true));
}
toolbar.$file.show();
var displayFile = function (ev, sizeMb, CB) {
var called_back;
var cb = function (e) {
if (called_back) { return; }
called_back = true;
if (CB) { CB(e); }
};
var $mt = $dlview.find('media-tag');
$mt.attr('src', src);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
// Add pad attributes when the file is saved in the drive
Title.onTitleChange(function () {
var owners = metadata.owners;
if (owners) { common.setPadAttribute('owners', owners); }
common.setPadAttribute('fileType', metadata.type);
});
$(document).on('cpPadStored', function () {
var owners = metadata.owners;
if (owners) { common.setPadAttribute('owners', owners); }
common.setPadAttribute('fileType', metadata.type);
});
var rightsideDisplayed = false;
// Save to the drive or update the acces time
var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle);
MediaTag($mt[0]).on('complete', function (decrypted) {
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
var owners = metadata.owners;
if (owners) {
common.setPadAttribute('owners', owners);
}
if (metadata.type) {
common.setPadAttribute('fileType', metadata.type);
}
if (!rightsideDisplayed) {
toolbar.$drawer
.append(common.createButton('export', true, {}, function () {
saveAs(decrypted.content, decrypted.metadata.name);
}));
rightsideDisplayed = true;
}
toolbar.addElement(['pageTitle'], {
pageTitle: title,
title: Title.getTitleConfig(),
});
toolbar.$drawer.append(common.createButton('forget', true));
toolbar.$drawer.append(common.createButton('properties', true));
if (common.isLoggedIn()) {
toolbar.$drawer.append(common.createButton('hashtag', true));
}
toolbar.$file.show();
var displayFile = function (ev, sizeMb, CB) {
var called_back;
var cb = function (e) {
if (called_back) { return; }
called_back = true;
if (CB) { CB(e); }
};
var $mt = $dlview.find('media-tag');
$mt.attr('src', src);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false;
MediaTag($mt[0], {
force: true // Download starts automatically
}).on('complete', function (decrypted) {
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
if (!rightsideDisplayed) {
toolbar.$drawer
.append(common.createButton('export', true, {}, function () {
saveAs(decrypted.content, decrypted.metadata.name);
}));
rightsideDisplayed = true;
}
// make pdfs big
var toolbarHeight = $('#cp-toolbar').height();
var $another_iframe = $('media-tag iframe').css({
'height': 'calc(100vh - ' + toolbarHeight + 'px)',
'width': '100vw',
'position': 'absolute',
'bottom': 0,
'left': 0,
'border': 0
});
// make pdfs big
var toolbarHeight = $('#cp-toolbar').height();
var $another_iframe = $('media-tag iframe').css({
'height': 'calc(100vh - ' + toolbarHeight + 'px)',
'width': '100vw',
'position': 'absolute',
'bottom': 0,
'left': 0,
'border': 0
if ($another_iframe.length) {
$another_iframe.load(function () {
cb();
});
} else {
cb();
}
}).on('progress', function (data) {
if (data.progress > 75) { return; }
var p = data.progress +'%';
$progress.width(p);
$progressTxt.text(Math.floor(data.progress) + '%');
}).on('error', function (err) {
console.error(err);
});
};
if ($another_iframe.length) {
$another_iframe.load(function () {
cb();
// XXX Update "download_button" key to use "download" first and "decrypt" second
var todoBigFile = function (sizeMb) {
$dlform.show();
UI.removeLoadingScreen();
var button = h('button.btn.btn-primary', {
title: Messages.download_button
}, Messages.download_button);
$dlform.append([
h('h2', Util.fixHTML(metadata.name)),
h('div.cp-button-container', [
button,
progressTxt
]),
]);
// don't display the size if you don't know it.
if (typeof(sizeMb) === 'number') {
$dlform.find('h2').append(' - ' +
Messages._getKey('formattedMB', [sizeMb]));
}
var decrypting = false;
var onClick = function (ev) {
if (decrypting) { return; }
decrypting = true;
$(button).prop('disabled', 'disabled');
$dlform.append(progressContainer);
displayFile(ev, sizeMb, function (err) {
$appContainer.css('background-color',
common.getAppConfig().appBackgroundColor);
if (err) { UI.alert(err); }
});
} else {
cb();
};
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
$(button).click(onClick);
};
common.getFileSize(hexFileName, function (e, data) {
if (e) {
return void UI.errorLoadingScreen(e);
}
}).on('progress', function (data) {
var p = data.progress +'%';
$progress.width(p);
}).on('error', function (err) {
console.error(err);
var size = Util.bytesToMegabytes(data);
return void todoBigFile(size);
});
};
var todoBigFile = function (sizeMb) {
$dlform.show();
UI.removeLoadingScreen();
$dllabel.append($('<br>'));
$dllabel.append(Util.fixHTML(metadata.name));
// don't display the size if you don't know it.
if (typeof(sizeM) === 'number') {
$dllabel.append($('<br>'));
$dllabel.append(Messages._getKey('formattedMB', [sizeMb]));
}
var decrypting = false;
var onClick = function (ev) {
if (decrypting) { return; }
decrypting = true;
displayFile(ev, sizeMb, function (err) {
$appContainer.css('background-color',
common.getAppConfig().appBackgroundColor);
if (err) { UI.alert(err); }
});
};
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
$dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick);
};
common.getFileSize(hexFileName, function (e, data) {
if (e) {
return void UI.errorLoadingScreen(e);
}
var size = Util.bytesToMegabytes(data);
return void todoBigFile(size);
});
});
})();
return;
}

@ -3,47 +3,17 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
var hash, href;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/file/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
}).nThen(function (/*waitFor*/) {
var addData = function (meta, Cryptpad) {
meta.filehash = Cryptpad.currentPad.hash;

@ -3,7 +3,7 @@
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<script async data-bootload="/kanban/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/kanban/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden {
display: none;

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/notifications/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/notifications/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -3,38 +3,14 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/notifications/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
var category;
if (window.location.hash) {

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/common/onlyoffice/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/common/onlyoffice/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/common/onlyoffice/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/common/onlyoffice/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -27,6 +27,7 @@ body.cp-app-pad {
#cp-app-pad-toc {
@toc-level-indent: 15px;
overflow-y: auto;
margin-top: 10px;
margin-left: 10px;
width: 200px;

@ -2,7 +2,7 @@
<html class="cp-app-noscroll cp-app-print">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;

@ -462,7 +462,7 @@ define([
setTimeout(function() { // Just in case
var tags = dom.querySelectorAll('media-tag:empty');
Array.prototype.slice.call(tags).forEach(function(el) {
MediaTag(el);
var mediaObject = MediaTag(el);
$(el).on('keydown', function(e) {
if ([8, 46].indexOf(e.which) !== -1) {
$(el).remove();
@ -474,6 +474,7 @@ define([
if (mutation.type === 'childList') {
var list_values = [].slice.call(el.children);
mediaTagMap[el.getAttribute('src')] = list_values;
if (mediaObject.complete) { observer.disconnect(); }
}
});
});
@ -492,7 +493,7 @@ define([
var src = tag.getAttribute('src');
if (mediaTagMap[src]) {
mediaTagMap[src].forEach(function(n) {
tag.appendChild(n.cloneNode());
tag.appendChild(n.cloneNode(true));
});
}
});
@ -650,9 +651,25 @@ define([
}, 500); // 500ms to make sure it is sent after chainpad sync
};
var isAnchor = function (el) { return el.nodeName === 'A'; };
var getAnchorName = function (el) {
return el.getAttribute('id') ||
el.getAttribute('data-cke-saved-name') ||
el.getAttribute('name') ||
Util.stripTags($(el).text());
};
var updateTOC = Util.throttle(function () {
var toc = [];
$inner.find('h1, h2, h3').each(function (i, el) {
$inner.find('h1, h2, h3, a[id][data-cke-saved-name]').each(function (i, el) {
if (isAnchor(el)) {
return void toc.push({
level: 2,
el: el,
title: getAnchorName(el),
});
}
toc.push({
level: Number(el.tagName.slice(1)),
el: el,
@ -661,6 +678,8 @@ define([
});
var content = [h('h2', Messages.markdown_toc)];
toc.forEach(function (obj) {
var title = (obj.title || "").trim();
if (!title) { return; }
// Only include level 2 headings
var level = obj.level;
var a = h('a.cp-pad-toc-link', {
@ -672,7 +691,7 @@ define([
if (!obj.el || UIElements.isVisible(obj.el, $inner)) { return; }
obj.el.scrollIntoView();
});
a.innerHTML = obj.title;
a.innerHTML = title;
content.push(h('p.cp-pad-toc-'+level, a));
});
$toc.html('').append(content);
@ -1098,7 +1117,7 @@ define([
*/
Ckeditor.dom.element.prototype.setHtml = function(a){
if (/callFunction/.test(a)) {
a = a.replace(/on(mousedown|blur|keydown|focus|click|dragstart)/g, function (value) {
a = a.replace(/on(mousedown|blur|keydown|focus|click|dragstart|mouseover|mouseout)/g, function (value) {
return 'o' + value;
});
}

@ -3,7 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/poll/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/poll/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -1087,6 +1087,8 @@ define([
common.openPadChat(function () {});
UI.removeLoadingScreen();
common.checkTrimHistory();
};
var onError = function (info) {

@ -3,47 +3,17 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
var hash, href;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/poll/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
hash: hash,

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/profile/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/profile/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -3,38 +3,14 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/profile/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils, cb) {
var Hash = Utils.Hash;

@ -2,7 +2,7 @@
<html style="height: 100%; background: transparent;">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/secureiframe/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/secureiframe/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
body #cp-loading {

@ -4,7 +4,8 @@ define([
'/api/config',
'jquery',
'/common/requireconfig.js',
], function (nThen, ApiConfig, $, RequireConfig) {
'/customize/messages.js',
], function (nThen, ApiConfig, $, RequireConfig, Messages) {
var requireConfig = RequireConfig();
var ready = false;
@ -15,10 +16,12 @@ define([
nThen(function (waitFor) {
$(waitFor());
}).nThen(function (waitFor) {
var lang = Messages._languageUsed;
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
pfx: window.location.origin,
lang: lang
};
window.rc = requireConfig;
window.apiconf = ApiConfig;

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/settings/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/settings/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -3,38 +3,14 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/settings/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('Q_THUMBNAIL_CLEAR', function (d, cb) {

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/common/onlyoffice/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/common/onlyoffice/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -2,7 +2,7 @@
<html class="cp-app-noscroll cp-app-print">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/slide/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/slide/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/support/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/support/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -3,40 +3,16 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
'/common/outer/local-store.js',
'/common/outer/login-block.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO, LocalStore, Block) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO, LocalStore, Block) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/support/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
var category;
if (window.location.hash) {

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/teams/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/teams/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -3,47 +3,17 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
var hash, href;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/teams/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
}).nThen(function (/*waitFor*/) {
var teamId;
var addRpc = function (sframeChan, Cryptpad, Utils) {
@ -127,9 +97,12 @@ define([
var secret = Hash.getSecrets('team', hash);
cb(null, secret);
};
var addData = function (meta) {
if (!hash) { return; }
meta.teamInviteHash = hash.slice(1);
var addData = function (meta, Cryptpad, user, Utils) {
if (!Utils.currentPad.hash) { return; }
var _hash = Utils.currentPad.hash.replace(/^#/, '');
var parsed = Utils.Hash.parseTypeHash('invite', _hash);
if (parsed.app !== 'invite') { return; }
meta.teamInviteHash = _hash;
};
SFCommonO.start({
getSecrets: getSecrets,

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/todo/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/todo/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/whiteboard/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/whiteboard/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }

@ -2,7 +2,7 @@
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/worker/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/worker/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
</style>

@ -3,38 +3,14 @@ define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
], function (nThen, ApiConfig, DomReady, SFCommonO) {
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/worker/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 = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
SFCommonO.initIframe(waitFor);
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
noRealtime: true,

Loading…
Cancel
Save