Merge branch 'staging' into soon
commit
ef604422ab
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -1,3 +1,38 @@
|
|||
# 4.6.0
|
||||
|
||||
## Goals
|
||||
|
||||
* work on polls/surveys
|
||||
* stabilize and implement tests
|
||||
|
||||
## Update notes
|
||||
|
||||
* checkup/server/config
|
||||
* test for anti-FLoC header
|
||||
* add anti-FloC header to server so the default dev server passes all tests
|
||||
* update NGINX example to avoid duplicated headers
|
||||
* simplify dev server headers
|
||||
* adjust table borders for dark/light mode
|
||||
* say what headers are wrong
|
||||
* rename exported object in `application_config_internal.js` to avoid copypasta errors
|
||||
* `AppConfig.disableAnonymousPadCreation = false;`
|
||||
* note that it's only enforced client-side
|
||||
* update lodash devDependency
|
||||
|
||||
## Features
|
||||
|
||||
* generate `supportMailbox` keys via the admin panel
|
||||
* document this
|
||||
* markdown preview
|
||||
* code blocks are full width
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* fix opening links from temporary shared folders on iphone or other contexts that do not support shared workers
|
||||
* show "features" instead of "pricing" in static pages' footer when premium subscriptions are not available
|
||||
* use preferred 12/24h time format in date picker
|
||||
* add error handling to admin panel decree RPCs
|
||||
|
||||
# 4.5.0
|
||||
|
||||
## Goals
|
||||
|
|
|
@ -122,19 +122,6 @@ module.exports = {
|
|||
],
|
||||
*/
|
||||
|
||||
/* CryptPad's administration panel includes a "support" tab
|
||||
* wherein administrators with a secret key can view messages
|
||||
* sent from users via the encrypted forms on the /support/ page
|
||||
*
|
||||
* To enable this functionality:
|
||||
* run `node ./scripts/generate-admin-keys.js`
|
||||
* save the public key in your config in the value below
|
||||
* add the private key via the admin panel
|
||||
* and back it up in a secure manner
|
||||
*
|
||||
*/
|
||||
// supportMailboxPublicKey: "",
|
||||
|
||||
/* We're very proud that CryptPad is available to the public as free software!
|
||||
* We do, however, still need to pay our bills as we develop the platform.
|
||||
*
|
||||
|
@ -147,11 +134,6 @@ module.exports = {
|
|||
*/
|
||||
//removeDonateButton: false,
|
||||
|
||||
/* CryptPad will display a point of contact for your instance on its contact page
|
||||
* (/contact.html) if you provide it below.
|
||||
*/
|
||||
adminEmail: 'i.did.not.read.my.config@cryptpad.fr',
|
||||
|
||||
/*
|
||||
* By default, CryptPad contacts one of our servers once a day.
|
||||
* This check-in will also send some very basic information about your instance including its
|
||||
|
|
|
@ -43,6 +43,17 @@ define([
|
|||
return Pages.externalLink(el, Pages.localizeDocsLink(href));
|
||||
};
|
||||
|
||||
var accounts = Pages.accounts = {
|
||||
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
|
||||
upgradeURL: AppConfig.upgradeURL
|
||||
};
|
||||
|
||||
Pages.areSubscriptionsAllowed = function () {
|
||||
try {
|
||||
return ApiConfig.allowSubscriptions && accounts.upgradeURL && !ApiConfig.restrictRegistration;
|
||||
} catch (err) { return void console.error(err); }
|
||||
};
|
||||
|
||||
var languageSelector = function () {
|
||||
var options = [];
|
||||
var languages = Msg._languages;
|
||||
|
@ -94,7 +105,7 @@ define([
|
|||
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
|
||||
'/imprint.html' : AppConfig.imprint);
|
||||
|
||||
Pages.versionString = "v4.5.0";
|
||||
Pages.versionString = "v4.6.0";
|
||||
|
||||
|
||||
// used for the about menu
|
||||
|
@ -133,7 +144,7 @@ define([
|
|||
footerCol('footer_product', [
|
||||
footLink('/what-is-cryptpad.html', 'topbar_whatIsCryptpad'),
|
||||
Pages.docsLink,
|
||||
footLink('/features.html', 'pricing'),
|
||||
footLink('/features.html', Pages.areSubscriptionsAllowed()? 'pricing': 'features'), // Messages.pricing, Messages.features
|
||||
Pages.githubLink,
|
||||
footLink('https://opencollective.com/cryptpad/contribute/', 'footer_donate'),
|
||||
]),
|
||||
|
|
|
@ -8,10 +8,7 @@ define([
|
|||
'/api/config',
|
||||
'/common/common-ui-elements.js',
|
||||
], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements) {
|
||||
var accounts = {
|
||||
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
|
||||
upgradeURL: AppConfig.upgradeURL
|
||||
};
|
||||
var accounts = Pages.accounts;
|
||||
|
||||
return function () {
|
||||
Msg.features_f_apps_note = AppConfig.availablePadTypes.map(function (app) {
|
||||
|
@ -145,10 +142,11 @@ define([
|
|||
]),
|
||||
]),
|
||||
]);
|
||||
var availableFeatures =
|
||||
(Config.allowSubscriptions && accounts.upgradeURL && !Config.restrictRegistration) ?
|
||||
[anonymousFeatures, registeredFeatures, premiumFeatures] :
|
||||
[anonymousFeatures, registeredFeatures];
|
||||
var availableFeatures = [
|
||||
anonymousFeatures,
|
||||
registeredFeatures,
|
||||
Pages.areSubscriptionsAllowed() ? premiumFeatures: undefined,
|
||||
];
|
||||
|
||||
return h('div#cp-main', [
|
||||
Pages.infopageTopbar(),
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
pre > code {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
padding: 5px;
|
||||
overflow-x: auto;
|
||||
|
|
|
@ -167,6 +167,13 @@ server {
|
|||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# These settings prevent both NGINX and the API server
|
||||
# from setting the same headers and creating duplicates
|
||||
proxy_hide_header Cross-Origin-Resource-Policy;
|
||||
add_header Cross-Origin-Resource-Policy cross-origin;
|
||||
proxy_hide_header Cross-Origin-Embedder-Policy;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
}
|
||||
|
||||
# encrypted blobs are immutable and are thus cached for a year
|
||||
|
|
|
@ -348,6 +348,9 @@ var commands = {
|
|||
|
||||
CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex,
|
||||
GET_CACHED_CHANNEL_INDEX: getChannelIndex,
|
||||
// TODO implement admin historyTrim
|
||||
// TODO implement kick from channel
|
||||
// TODO implement force-disconnect user(s)?
|
||||
|
||||
CLEAR_CACHED_CHANNEL_METADATA: clearChannelMetadata,
|
||||
GET_CACHED_CHANNEL_METADATA: getChannelMetadata,
|
||||
|
|
|
@ -48,6 +48,7 @@ Default.httpHeaders = function () {
|
|||
"X-XSS-Protection": "1; mode=block",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Permissions-policy":"interest-cohort=()"
|
||||
};
|
||||
};
|
||||
Default.mainPages = function () {
|
||||
|
|
|
@ -701,6 +701,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
}
|
||||
|
||||
if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
|
||||
// TODO this might be a good place to reject channel creation by anonymous users
|
||||
handleFirstMessage(Env, channelName, metadata);
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cryptpad",
|
||||
"version": "4.5.0",
|
||||
"version": "4.6.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "cryptpad",
|
||||
"description": "realtime collaborative visual editor with zero knowlege server",
|
||||
"version": "4.5.0",
|
||||
"version": "4.6.0",
|
||||
"license": "AGPL-3.0+",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
17
server.js
17
server.js
|
@ -108,28 +108,21 @@ var setHeaders = (function () {
|
|||
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '',
|
||||
"Cross-Origin-Embedder-Policy": 'require-corp',
|
||||
});
|
||||
|
||||
if (Env.NO_SANDBOX) { // handles correct configuration for local development
|
||||
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Resource-Policy": 'cross-origin',
|
||||
"Cross-Origin-Embedder-Policy": 'require-corp',
|
||||
});
|
||||
}
|
||||
|
||||
// Don't set CSP headers on /api/config because they aren't necessary and they cause problems
|
||||
// Don't set CSP headers on /api/ endpoints
|
||||
// because they aren't necessary and they cause problems
|
||||
// when duplicated by NGINX in production environments
|
||||
if (/^\/api\/(broadcast|config)/.test(req.url)) {
|
||||
/*
|
||||
if (Env.NO_SANDBOX) {
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Resource-Policy": 'cross-origin',
|
||||
});
|
||||
}
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (/^\/api\/(broadcast|config)/.test(req.url)) { return; }
|
||||
|
||||
applyHeaderMap(res, {
|
||||
"Cross-Origin-Resource-Policy": 'cross-origin',
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ define([
|
|||
'/support/ui.js',
|
||||
|
||||
'/lib/datepicker/flatpickr.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
|
||||
'css!/lib/datepicker/flatpickr.min.css',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
|
@ -44,6 +45,7 @@ define([
|
|||
'instanceStatus': {}
|
||||
};
|
||||
|
||||
var Nacl = window.nacl;
|
||||
var common;
|
||||
var sFrameChan;
|
||||
|
||||
|
@ -54,6 +56,7 @@ define([
|
|||
'cp-admin-archive',
|
||||
'cp-admin-unarchive',
|
||||
'cp-admin-registration',
|
||||
'cp-admin-email'
|
||||
],
|
||||
'quota': [ // Msg.admin_cat_quota
|
||||
'cp-admin-defaultlimit',
|
||||
|
@ -71,7 +74,8 @@ define([
|
|||
],
|
||||
'support': [ // Msg.admin_cat_support
|
||||
'cp-admin-support-list',
|
||||
'cp-admin-support-init'
|
||||
'cp-admin-support-init',
|
||||
'cp-admin-support-priv',
|
||||
],
|
||||
'broadcast': [ // Msg.admin_cat_broadcast
|
||||
'cp-admin-maintenance',
|
||||
|
@ -267,8 +271,11 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['RESTRICT_REGISTRATION', [val]]
|
||||
}, function (e) {
|
||||
if (e) { UI.warn(Messages.error); console.error(e); }
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
}
|
||||
APP.updateStatus(function () {
|
||||
spinner.done();
|
||||
state = APP.instanceStatus.restrictRegistration;
|
||||
|
@ -282,6 +289,45 @@ define([
|
|||
return $div;
|
||||
};
|
||||
|
||||
create['email'] = function () {
|
||||
var key = 'email';
|
||||
var $div = makeBlock(key, true); // Msg.admin_emailHint, Msg.admin_emailTitle, Msg.admin_emailButton
|
||||
var $button = $div.find('button');
|
||||
|
||||
var input = h('input', {
|
||||
type: 'email',
|
||||
value: ApiConfig.adminEmail || ''
|
||||
});
|
||||
var $input = $(input);
|
||||
var innerDiv = h('div.cp-admin-setlimit-form', input);
|
||||
var spinner = UI.makeSpinner($(innerDiv));
|
||||
|
||||
$button.click(function () {
|
||||
if (!$input.val()) { return; }
|
||||
spinner.spin();
|
||||
$button.attr('disabled', 'disabled');
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_ADMIN_EMAIL', [$input.val()]]
|
||||
}, function (e, response) {
|
||||
$button.removeAttr('disabled');
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
$input.val('');
|
||||
console.error(e, response);
|
||||
spinner.hide();
|
||||
return;
|
||||
}
|
||||
spinner.done();
|
||||
UI.log(Messages.saved);
|
||||
});
|
||||
});
|
||||
|
||||
$button.before(innerDiv);
|
||||
|
||||
return $div;
|
||||
};
|
||||
|
||||
var getPrettySize = UIElements.prettySize;
|
||||
|
||||
create['defaultlimit'] = function () {
|
||||
|
@ -316,8 +362,11 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['UPDATE_DEFAULT_STORAGE', data]
|
||||
}, function (e) {
|
||||
if (e) { UI.warn(Messages.error); return void console.error(e); }
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
return void console.error(e, response);
|
||||
}
|
||||
var limit = getPrettySize(l);
|
||||
$div.find('.cp-admin-defaultlimit-value').text(Messages._getKey('admin_limit', [limit]));
|
||||
});
|
||||
|
@ -448,8 +497,12 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['RM_QUOTA', data]
|
||||
}, function (e) {
|
||||
if (e) { UI.warn(Messages.error); console.error(e); }
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
return;
|
||||
}
|
||||
APP.refreshLimits();
|
||||
$key.val('');
|
||||
});
|
||||
|
@ -462,8 +515,12 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_QUOTA', data]
|
||||
}, function (e) {
|
||||
if (e) { UI.warn(Messages.error); console.error(e); }
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
return;
|
||||
}
|
||||
APP.refreshLimits();
|
||||
$key.val('');
|
||||
});
|
||||
|
@ -637,8 +694,13 @@ define([
|
|||
};
|
||||
|
||||
var supportKey = ApiConfig.supportMailbox;
|
||||
var checkAdminKey = function (priv) {
|
||||
if (!supportKey) { return; }
|
||||
return Hash.checkBoxKeyPair(priv, supportKey);
|
||||
};
|
||||
|
||||
create['support-list'] = function () {
|
||||
if (!supportKey || !APP.privateKey) { return; }
|
||||
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
|
||||
var $container = makeBlock('support-list'); // Msg.admin_supportListHint, .admin_supportListTitle
|
||||
var $div = $(h('div.cp-support-container')).appendTo($container);
|
||||
|
||||
|
@ -898,16 +960,63 @@ define([
|
|||
return $container;
|
||||
};
|
||||
|
||||
create['support-priv'] = function () {
|
||||
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
|
||||
|
||||
var checkAdminKey = function (priv) {
|
||||
if (!supportKey) { return; }
|
||||
return Hash.checkBoxKeyPair(priv, supportKey);
|
||||
var $div = makeBlock('support-priv', true); // Msg.admin_supportPrivHint, .admin_supportPrivTitle, .admin_supportPrivButton
|
||||
var $button = $div.find('button').click(function () {
|
||||
$button.remove();
|
||||
var $selectable = $(UI.dialog.selectable(APP.privateKey)).css({ 'max-width': '28em' });
|
||||
$div.append($selectable);
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
create['support-init'] = function () {
|
||||
var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle
|
||||
if (!supportKey) {
|
||||
$div.append(h('p', Messages.admin_supportInitHelp));
|
||||
(function () {
|
||||
$div.append(h('p', Messages.admin_supportInitHelp));
|
||||
var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate);
|
||||
var $button = $(button).appendTo($div);
|
||||
$div.append($button);
|
||||
var spinner = UI.makeSpinner($div);
|
||||
$button.click(function () {
|
||||
spinner.spin();
|
||||
$button.attr('disabled', 'disabled');
|
||||
var keyPair = Nacl.box.keyPair();
|
||||
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
|
||||
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
|
||||
// Store the private key first. It won't be used until the decree is accepted.
|
||||
sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) {
|
||||
if (err || (obj && obj.error)) {
|
||||
console.error(err || obj.error);
|
||||
UI.warn(Messages.error);
|
||||
spinner.hide();
|
||||
return;
|
||||
}
|
||||
// Then send the decree
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_SUPPORT_MAILBOX', [pub]]
|
||||
}, function (e, response) {
|
||||
$button.removeAttr('disabled');
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
spinner.hide();
|
||||
return;
|
||||
}
|
||||
spinner.done();
|
||||
UI.log(Messages.saved);
|
||||
supportKey = pub;
|
||||
APP.privateKey = priv;
|
||||
$('.cp-admin-support-init').hide();
|
||||
APP.$rightside.append(create['support-list']());
|
||||
APP.$rightside.append(create['support-priv']());
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
return $div;
|
||||
}
|
||||
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
|
||||
|
@ -937,6 +1046,7 @@ define([
|
|||
APP.privateKey = key;
|
||||
$('.cp-admin-support-init').hide();
|
||||
APP.$rightside.append(create['support-list']());
|
||||
APP.$rightside.append(create['support-priv']());
|
||||
});
|
||||
});
|
||||
return $div;
|
||||
|
@ -1030,9 +1140,10 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_LAST_BROADCAST_HASH', [lastHash]]
|
||||
}, function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
return;
|
||||
}
|
||||
console.log('lastBroadcastHash updated');
|
||||
|
@ -1299,19 +1410,23 @@ define([
|
|||
var $start = $(start);
|
||||
var $end = $(end);
|
||||
var is24h = false;
|
||||
var dateFormat = "Y-m-d H:i";
|
||||
try {
|
||||
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
|
||||
} catch (e) {}
|
||||
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
|
||||
|
||||
var endPickr = Flatpickr(end, {
|
||||
enableTime: true,
|
||||
time_24hr: is24h,
|
||||
dateFormat: dateFormat,
|
||||
minDate: new Date()
|
||||
});
|
||||
Flatpickr(start, {
|
||||
enableTime: true,
|
||||
time_24hr: is24h,
|
||||
minDate: new Date(),
|
||||
dateFormat: dateFormat,
|
||||
onChange: function () {
|
||||
endPickr.set('minDate', new Date($start.val()));
|
||||
}
|
||||
|
@ -1336,9 +1451,10 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_MAINTENANCE', [data]]
|
||||
}, function (e) {
|
||||
if (e) {
|
||||
UI.warn(Messages.error); console.error(e);
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
$button.prop('disabled', '');
|
||||
return;
|
||||
}
|
||||
|
@ -1430,10 +1546,11 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_SURVEY_URL', [data]]
|
||||
}, function (e) {
|
||||
if (e) {
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
$button.prop('disabled', '');
|
||||
UI.warn(Messages.error); console.error(e);
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
return;
|
||||
}
|
||||
// Maintenance applied, send notification
|
||||
|
@ -1529,11 +1646,12 @@ define([
|
|||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'GET_WORKER_PROFILES',
|
||||
}, function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (e || data.error) {
|
||||
UI.warn(Messages.error);
|
||||
return void console.error(e, data);
|
||||
}
|
||||
//console.info(data);
|
||||
$div.find("table").remove();
|
||||
|
||||
|
||||
process(data);
|
||||
$div.append(table);
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ html, body {
|
|||
}
|
||||
|
||||
.pending {
|
||||
border: 1px solid white;
|
||||
border: 1px solid @cryptpad_text_col;
|
||||
.fa {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ html, body {
|
|||
table {
|
||||
td {
|
||||
padding: 5px;
|
||||
border: 1px solid white;
|
||||
border: 1px solid @cryptpad_text_col;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ define([
|
|||
]);
|
||||
};
|
||||
|
||||
var cacheBuster = function (url) {
|
||||
return url + '?test=' + (+new Date());
|
||||
};
|
||||
|
||||
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
|
||||
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
|
||||
|
||||
|
@ -117,7 +121,7 @@ define([
|
|||
|
||||
var checkAvailability = function (url, cb) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
url: cacheBuster(url),
|
||||
data: {},
|
||||
complete: function (xhr) {
|
||||
cb(xhr.status === 200);
|
||||
|
@ -169,10 +173,13 @@ define([
|
|||
}).nThen(function () {
|
||||
// Iframe is loaded
|
||||
clearTimeout(to);
|
||||
console.log("removing sandbox iframe");
|
||||
$('iframe#sbox-iframe').remove();
|
||||
cb(true);
|
||||
});
|
||||
});
|
||||
|
||||
var shared_websocket;
|
||||
// Test Websocket
|
||||
var evWSError = Util.mkEvent(true);
|
||||
assert(function (_cb, msg) {
|
||||
|
@ -185,6 +192,7 @@ define([
|
|||
}));
|
||||
|
||||
var ws = new WebSocket(NetConfig.getWebsocketURL());
|
||||
shared_websocket = ws;
|
||||
var to = setTimeout(function () {
|
||||
console.error('Websocket TIMEOUT');
|
||||
evWSError.fire();
|
||||
|
@ -203,6 +211,7 @@ define([
|
|||
});
|
||||
|
||||
// Test login block
|
||||
var shared_realtime;
|
||||
assert(function (_cb, msg) {
|
||||
var websocketErr = "No WebSocket available";
|
||||
var cb = Util.once(Util.both(_cb, function (status) {
|
||||
|
@ -237,7 +246,7 @@ define([
|
|||
var blockUrl = Login.Block.getBlockUrl(opt.blockKeys);
|
||||
var blockRequest = Login.Block.serialize("{}", opt.blockKeys);
|
||||
var removeRequest = Login.Block.remove(opt.blockKeys);
|
||||
console.log('Test block URL:', blockUrl);
|
||||
console.warn('Testing block URL (%s). One 404 is normal.', blockUrl);
|
||||
|
||||
var userHash = '/2/drive/edit/000000000000000000000000';
|
||||
var secret = Hash.getSecrets('drive', userHash);
|
||||
|
@ -264,7 +273,7 @@ define([
|
|||
console.error("Can't create new channel. This may also be a websocket issue.");
|
||||
return void cb(false);
|
||||
}
|
||||
RT = rt;
|
||||
shared_realtime = RT = rt;
|
||||
var proxy = rt.proxy;
|
||||
proxy.edPublic = opt.edPublic;
|
||||
proxy.edPrivate = opt.edPrivate;
|
||||
|
@ -330,14 +339,13 @@ define([
|
|||
}).nThen(function () {
|
||||
cb(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var sheetURL = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
|
||||
|
||||
assert(function (cb, msg) {
|
||||
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
|
||||
var url = sheetURL;
|
||||
var url = cacheBuster(sheetURL);
|
||||
var expect = {
|
||||
'cross-origin-resource-policy': 'cross-origin',
|
||||
'cross-origin-embedder-policy': 'require-corp',
|
||||
|
@ -366,37 +374,12 @@ define([
|
|||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
msg = msg;
|
||||
return void cb(true);
|
||||
/*
|
||||
msg.appendChild(h('span', [
|
||||
"The spreadsheet editor's code was not served with the required Content-Security Policy headers. ",
|
||||
"This is most often caused by incorrectly configured sandbox parameters (",
|
||||
h('code', 'httpUnsafeOrigin'),
|
||||
' and ',
|
||||
h('code', 'httpSafeOrigin'),
|
||||
' in ',
|
||||
CONFIG_PATH,
|
||||
"), or settings in your reverse proxy's configuration which don't match your application server's config. ",
|
||||
RESTART_WARNING(),
|
||||
]));
|
||||
|
||||
$.ajax(sheetURL, {
|
||||
msg.innerText = "Missing HTTP header required to disable Google's Floc.";
|
||||
$.ajax('/?'+ (+new Date()), {
|
||||
complete: function (xhr) {
|
||||
var csp = xhr.getResponseHeader('Content-Security-Policy');
|
||||
if (!/unsafe\-eval/.test(csp)) {
|
||||
// OnlyOffice requires unsafe-eval
|
||||
console.error('CSP', csp);
|
||||
return cb("expected 'unsafe-eval'");
|
||||
}
|
||||
if (!/unsafe\-inline/.test(csp)) {
|
||||
// OnlyOffice also requires unsafe-inline
|
||||
console.error('CSP', csp);
|
||||
return cb("expected 'unsafe-inline'");
|
||||
}
|
||||
cb(true);
|
||||
cb(xhr.getResponseHeader('permissions-policy') === 'interest-cohort=()');
|
||||
},
|
||||
}); */
|
||||
});
|
||||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
|
@ -407,17 +390,20 @@ define([
|
|||
"Your browser console may provide more details as to why this resource could not be loaded. ",
|
||||
]));
|
||||
|
||||
$.ajax('/api/broadcast', {
|
||||
$.ajax(cacheBuster('/api/broadcast'), {
|
||||
dataType: 'text',
|
||||
complete: function (xhr) {
|
||||
console.log(xhr);
|
||||
cb(xhr.status === 200);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
var checkAPIHeaders = function (url, cb) {
|
||||
$.ajax(url, {
|
||||
var code = function (content) {
|
||||
return h('code', content);
|
||||
};
|
||||
|
||||
var checkAPIHeaders = function (url, msg, cb) {
|
||||
$.ajax(cacheBuster(url), {
|
||||
dataType: 'text',
|
||||
complete: function (xhr) {
|
||||
var allHeaders = xhr.getAllResponseHeaders();
|
||||
|
@ -436,15 +422,31 @@ define([
|
|||
|
||||
var expect = {
|
||||
'cross-origin-resource-policy': 'cross-origin',
|
||||
'cross-origin-embedder-policy': 'require-corp',
|
||||
};
|
||||
var incorrect = Object.keys(expect).some(function (k) {
|
||||
var incorrect = false;
|
||||
|
||||
Object.keys(expect).forEach(function (k) {
|
||||
var response = xhr.getResponseHeader(k);
|
||||
if (response !== expect[k]) {
|
||||
return true;
|
||||
var expected = expect[k];
|
||||
if (response !== expected) {
|
||||
incorrect = true;
|
||||
msg.appendChild(h('p', [
|
||||
'The ',
|
||||
code(k),
|
||||
' header for ',
|
||||
code(url),
|
||||
" is '",
|
||||
code(response),
|
||||
"' instead of '",
|
||||
code(expected),
|
||||
"' as expected.",
|
||||
]));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicated || incorrect) { console.error(allHeaders); }
|
||||
if (duplicated || incorrect) { console.debug(allHeaders); }
|
||||
cb(!duplicated && !incorrect);
|
||||
},
|
||||
});
|
||||
|
@ -455,13 +457,13 @@ define([
|
|||
assert(function (cb, msg) {
|
||||
var url = '/api/config';
|
||||
msg.innerText = url + INCORRECT_HEADER_TEXT;
|
||||
checkAPIHeaders(url, cb);
|
||||
checkAPIHeaders(url, msg, cb);
|
||||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
var url = '/api/broadcast';
|
||||
msg.innerText = url + INCORRECT_HEADER_TEXT;
|
||||
checkAPIHeaders(url, cb);
|
||||
checkAPIHeaders(url, msg, cb);
|
||||
});
|
||||
|
||||
var setWarningClass = function (msg) {
|
||||
|
@ -479,8 +481,9 @@ define([
|
|||
'This instance does not provide a valid ',
|
||||
h('code', 'adminEmail'),
|
||||
' which can make it difficult to contact its adminstrator to report vulnerabilities or abusive content.',
|
||||
' This can be configured in ', CONFIG_PATH(), '. ',
|
||||
RESTART_WARNING(),
|
||||
" This can be configured on your instance's admin panel. Use the provided ",
|
||||
code("Flush cache'"),
|
||||
" button for this change to take effect for all users.",
|
||||
]));
|
||||
cb(email);
|
||||
});
|
||||
|
@ -517,6 +520,156 @@ define([
|
|||
cb(false);
|
||||
});
|
||||
|
||||
var response = Util.response(function (err) {
|
||||
console.error('SANDBOX_ERROR', err);
|
||||
});
|
||||
|
||||
var sandboxIframe = h('iframe', {
|
||||
class: 'sandbox-test',
|
||||
src: cacheBuster(trimmedSafe + '/checkup/sandbox/index.html'),
|
||||
});
|
||||
document.body.appendChild(sandboxIframe);
|
||||
|
||||
var sandboxIframeReady = Util.mkEvent(true);
|
||||
setTimeout(function () {
|
||||
sandboxIframeReady.fire("TIMEOUT");
|
||||
}, 10 * 1000);
|
||||
|
||||
var postMessage = function (content, cb) {
|
||||
try {
|
||||
var txid = Util.uid();
|
||||
content.txid = txid;
|
||||
response.expect(txid, cb, 15000);
|
||||
sandboxIframe.contentWindow.postMessage(JSON.stringify(content), '*');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
var deferredPostMessage = function (content, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
nThen(function (w) {
|
||||
sandboxIframeReady.reg(w(function (err) {
|
||||
if (!err) { return; }
|
||||
w.abort();
|
||||
cb(err);
|
||||
}));
|
||||
}).nThen(function () {
|
||||
postMessage(content, cb);
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
try {
|
||||
var msg = JSON.parse(event.data);
|
||||
if (msg.command === 'READY') { return void sandboxIframeReady.fire(); }
|
||||
if (msg.q === "READY") { return; } // ignore messages from the usual sandboxed iframe
|
||||
var txid = msg.txid;
|
||||
if (!txid) { return console.log("no handler for ", txid); }
|
||||
response.handle(txid, msg.content);
|
||||
} catch (err) {
|
||||
console.error(event);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
var parseCSP = function (CSP) {
|
||||
//console.error(CSP);
|
||||
var CSP_headers = {};
|
||||
CSP.split(";")
|
||||
.forEach(function (rule) {
|
||||
rule = (rule || "").trim();
|
||||
if (!rule) { return; }
|
||||
var parts = rule.split(/\s/);
|
||||
var first = parts[0];
|
||||
var rest = rule.slice(first.length + 1);
|
||||
CSP_headers[first] = rest;
|
||||
//console.error(rule.trim());
|
||||
//console.info("[%s] '%s'", first, rest);
|
||||
});
|
||||
return CSP_headers;
|
||||
};
|
||||
|
||||
var hasUnsafeEval = function (CSP_headers) {
|
||||
return /unsafe\-eval/.test(CSP_headers['script-src']);
|
||||
};
|
||||
|
||||
var hasUnsafeInline = function (CSP_headers) {
|
||||
return /unsafe\-inline/.test(CSP_headers['script-src']);
|
||||
};
|
||||
|
||||
var hasOnlyOfficeHeaders = function (CSP_headers) {
|
||||
if (!hasUnsafeEval(CSP_headers)) {
|
||||
console.error("NO_UNSAFE_EVAL");
|
||||
console.log(CSP_headers);
|
||||
return false;
|
||||
}
|
||||
if (!hasUnsafeInline(CSP_headers)) {
|
||||
console.error("NO_UNSAFE_INLINE");
|
||||
return void false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var CSP_WARNING = function (url) {
|
||||
return h('span', [
|
||||
code(url),
|
||||
' does not have the required ',
|
||||
code("'content-security-policy'"),
|
||||
' headers set. This is most often related to incorrectly configured sandbox domains or reverse proxies.',
|
||||
]);
|
||||
};
|
||||
|
||||
assert(function (_cb, msg) {
|
||||
var url = '/sheet/inner.html';
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
msg.appendChild(CSP_WARNING(url));
|
||||
deferredPostMessage({
|
||||
command: 'GET_HEADER',
|
||||
content: {
|
||||
url: url,
|
||||
header: 'content-security-policy',
|
||||
},
|
||||
}, function (content) {
|
||||
var CSP_headers = parseCSP(content);
|
||||
cb(hasOnlyOfficeHeaders(CSP_headers));
|
||||
});
|
||||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
|
||||
msg.appendChild(CSP_WARNING(url));
|
||||
deferredPostMessage({
|
||||
command: 'GET_HEADER',
|
||||
content: {
|
||||
url: url,
|
||||
header: 'content-security-policy',
|
||||
},
|
||||
}, function (content) {
|
||||
var CSP_headers = parseCSP(content);
|
||||
cb(hasOnlyOfficeHeaders(CSP_headers));
|
||||
});
|
||||
});
|
||||
|
||||
assert(function (cb, msg) {
|
||||
var url = '/sheet/inner.html';
|
||||
msg.appendChild(h('span', [
|
||||
code(url),
|
||||
' does not have the required ',
|
||||
code("'cross-origin-opener-policy'"),
|
||||
' headers set.',
|
||||
]));
|
||||
deferredPostMessage({
|
||||
command: 'GET_HEADER',
|
||||
content: {
|
||||
url: url,
|
||||
header: 'cross-origin-opener-policy',
|
||||
},
|
||||
}, function (content) {
|
||||
cb(content === 'same-origin');
|
||||
});
|
||||
});
|
||||
|
||||
if (false) {
|
||||
assert(function (cb, msg) {
|
||||
msg.innerText = 'fake test to simulate failure';
|
||||
|
@ -583,6 +736,14 @@ define([
|
|||
|
||||
$progress.remove();
|
||||
$('body').prepend(report);
|
||||
try {
|
||||
console.log('closing shared websocket');
|
||||
shared_websocket.close();
|
||||
} catch (err) { console.error(err); }
|
||||
try {
|
||||
console.log('closing shared realtime');
|
||||
shared_realtime.network.disconnect();
|
||||
} catch (err) { console.error(err); }
|
||||
}, function (i, total) {
|
||||
console.log('test '+ i +' completed');
|
||||
completed++;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cp-progress"></div>
|
||||
<iframe-placeholder>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
define([
|
||||
'jquery',
|
||||
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/checkup/app-checkup.less',
|
||||
], function ($) {
|
||||
var postMessage = function (content) {
|
||||
window.parent.postMessage(JSON.stringify(content), '*');
|
||||
};
|
||||
postMessage({ command: "READY", });
|
||||
var getHeaders = function (url, cb) {
|
||||
$.ajax(url + "?test=" + (+new Date()), {
|
||||
dataType: 'text',
|
||||
complete: function (xhr) {
|
||||
var allHeaders = xhr.getAllResponseHeaders();
|
||||
return void cb(void 0, allHeaders, xhr);
|
||||
},
|
||||
});
|
||||
};
|
||||
var COMMANDS = {};
|
||||
COMMANDS.GET_HEADER = function (content, cb) {
|
||||
var url = content.url;
|
||||
getHeaders(url, function (err, headers, xhr) {
|
||||
cb(xhr.getResponseHeader(content.header));
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event && event.data) {
|
||||
try {
|
||||
//console.log(JSON.parse(event.data));
|
||||
var msg = JSON.parse(event.data);
|
||||
var command = msg.command;
|
||||
var txid = msg.txid;
|
||||
COMMANDS[command](msg.content, function (response) {
|
||||
// postMessage with same txid
|
||||
postMessage({
|
||||
txid: txid,
|
||||
content: response,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
} else {
|
||||
console.error(event);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -4,14 +4,14 @@
|
|||
* file (make a copy from /customize.dist/application_config.js)
|
||||
*/
|
||||
define(function() {
|
||||
var config = {};
|
||||
var AppConfig = {};
|
||||
|
||||
/* Select the buttons displayed on the main page to create new collaborative sessions.
|
||||
* Removing apps from the list will prevent users from accessing them. They will instead be
|
||||
* redirected to the drive.
|
||||
* You should never remove the drive from this list.
|
||||
*/
|
||||
config.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
|
||||
AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
|
||||
/*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts' /*, 'calendar' */];
|
||||
/* The registered only types are apps restricted to registered users.
|
||||
* You should never remove apps from this list unless you know what you're doing. The apps
|
||||
|
@ -20,7 +20,7 @@ define(function() {
|
|||
* users and these users will be redirected to the login page if they still try to access
|
||||
* the app
|
||||
*/
|
||||
config.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
|
||||
AppConfig.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
|
||||
|
||||
/* CryptPad is available is multiple languages, but only English and French are maintained
|
||||
* by the developers. The other languages may be outdated, and any missing string for a langauge
|
||||
|
@ -30,37 +30,37 @@ define(function() {
|
|||
* can be found at the top of the file `/customize.dist/messages.js`. The list should only
|
||||
* contain languages code ('en', 'fr', 'de', 'pt-br', etc.), not their full name.
|
||||
*/
|
||||
//config.availableLanguages = ['en', 'fr', 'de'];
|
||||
//AppConfig.availableLanguages = ['en', 'fr', 'de'];
|
||||
|
||||
/* You can display a link to the imprint (legal notice) of your website in the static pages
|
||||
* footer. To do so, you can either set the following value to `true` and create an imprint.html page
|
||||
* in the `customize` directory. You can also set it to an absolute URL if your imprint page already exists.
|
||||
*/
|
||||
config.imprint = false;
|
||||
// config.imprint = true;
|
||||
// config.imprint = 'https://xwiki.com/en/company/legal-notice';
|
||||
AppConfig.imprint = false;
|
||||
// AppConfig.imprint = true;
|
||||
// AppConfig.imprint = 'https://xwiki.com/en/company/legal-notice';
|
||||
|
||||
/* You can display a link to your own privacy policy in the static pages footer.
|
||||
* To do so, set the following value to the absolute URL of your privacy policy.
|
||||
*/
|
||||
// config.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
|
||||
// AppConfig.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
|
||||
|
||||
/* We (the project's developers) include the ability to display a 'Roadmap' in static pages footer.
|
||||
* This is disabled by default.
|
||||
* We use this to publish the project's development roadmap, but you can use it however you like.
|
||||
* To do so, set the following value to an absolute URL.
|
||||
*/
|
||||
//config.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
|
||||
//AppConfig.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
|
||||
|
||||
/* Cryptpad apps use a common API to display notifications to users
|
||||
* by default, notifications are hidden after 5 seconds
|
||||
* You can change their duration here (measured in milliseconds)
|
||||
*/
|
||||
config.notificationTimeout = 5000;
|
||||
config.disableUserlistNotifications = false;
|
||||
AppConfig.notificationTimeout = 5000;
|
||||
AppConfig.disableUserlistNotifications = false;
|
||||
|
||||
// Update the default colors available in the whiteboard application
|
||||
config.whiteboardPalette = [
|
||||
AppConfig.whiteboardPalette = [
|
||||
'#000000', // black
|
||||
'#FFFFFF', // white
|
||||
'#848484', // grey
|
||||
|
@ -82,14 +82,14 @@ define(function() {
|
|||
// Background color in the apps with centered content:
|
||||
// - file app in view mode
|
||||
// - rich text app when editor's width reduced in settings
|
||||
config.appBackgroundColor = '#666';
|
||||
AppConfig.appBackgroundColor = '#666';
|
||||
|
||||
// Set enableTemplates to false to remove the button allowing users to save a pad as a template
|
||||
// and remove the template category in CryptDrive
|
||||
config.enableTemplates = true;
|
||||
AppConfig.enableTemplates = true;
|
||||
|
||||
// Set enableHistory to false to remove the "History" button in all the apps.
|
||||
config.enableHistory = true;
|
||||
AppConfig.enableHistory = true;
|
||||
|
||||
/* user passwords are hashed with scrypt, and salted with their username.
|
||||
this value will be appended to the username, causing the resulting hash
|
||||
|
@ -101,15 +101,15 @@ define(function() {
|
|||
created. Changing it at a later time will break logins for all existing
|
||||
users.
|
||||
*/
|
||||
config.loginSalt = '';
|
||||
config.minimumPasswordLength = 8;
|
||||
AppConfig.loginSalt = '';
|
||||
AppConfig.minimumPasswordLength = 8;
|
||||
|
||||
// Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
|
||||
config.badStateTimeout = 30000;
|
||||
AppConfig.badStateTimeout = 30000;
|
||||
|
||||
// Customize the icon used for each application.
|
||||
// You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
|
||||
config.applicationsIcon = {
|
||||
AppConfig.applicationsIcon = {
|
||||
file: 'cptools-file',
|
||||
fileupload: 'cptools-file-upload',
|
||||
folderupload: 'cptools-folder-upload',
|
||||
|
@ -130,50 +130,49 @@ define(function() {
|
|||
|
||||
// Ability to create owned pads and expiring pads through a new pad creation screen.
|
||||
// The new screen can be disabled by the users in their settings page
|
||||
config.displayCreationScreen = true;
|
||||
AppConfig.displayCreationScreen = true;
|
||||
|
||||
// Prevent anonymous users from storing pads in their drive
|
||||
config.disableAnonymousStore = false;
|
||||
// NOTE: this is only enforced client-side as the server does not distinguish between users drives and pads
|
||||
AppConfig.disableAnonymousStore = false;
|
||||
// Prevent anonymous users from creating new pads (they can still access and edit existing ones)
|
||||
// NOTE: this is only enforced client-side and will not prevent malicious clients from storing data
|
||||
AppConfig.disableAnonymousPadCreation = false;
|
||||
|
||||
// Hide the usage bar in settings and drive
|
||||
//config.hideUsageBar = true;
|
||||
//AppConfig.hideUsageBar = true;
|
||||
|
||||
// Disable feedback for all the users and hide the settings part about feedback
|
||||
//config.disableFeedback = true;
|
||||
|
||||
// Add new options in the share modal (extend an existing tab or add a new tab).
|
||||
// More info about how to use it on the wiki:
|
||||
// https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
|
||||
//config.customizeShareOptions = function (hashes, tabs, config) {};
|
||||
//AppConfig.disableFeedback = true;
|
||||
|
||||
// Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
|
||||
// indicating if the user is registered or anonymous. Here you can change the way anonymous users
|
||||
// work in CryptPad, use an external SSO or even force registration
|
||||
// *NOTE*: You have to call the `callback` function to continue the loading process
|
||||
//config.beforeLogin = function(isLoggedIn, callback) {};
|
||||
//AppConfig.beforeLogin = function(isLoggedIn, callback) {};
|
||||
|
||||
// Add code to be executed on every page after the user object is loaded (also work for
|
||||
// unregistered users). This allows you to interact with your users' drive
|
||||
// *NOTE*: You have to call the `callback` function to continue the loading process
|
||||
//config.afterLogin = function(api, callback) {};
|
||||
//AppConfig.afterLogin = function(api, callback) {};
|
||||
|
||||
// Disabling the profile app allows you to import the profile informations (display name, avatar)
|
||||
// from an external source and make sure the users can't change them from CryptPad.
|
||||
// You can use config.afterLogin to import these values in the users' drive.
|
||||
//config.disableProfile = true;
|
||||
// You can use AppConfig.afterLogin to import these values in the users' drive.
|
||||
//AppConfig.disableProfile = true;
|
||||
|
||||
// Disable the use of webworkers and sharedworkers in CryptPad.
|
||||
// Workers allow us to run the websockets connection and open the user drive in a separate thread.
|
||||
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
|
||||
// making it much faster to open new tabs.
|
||||
config.disableWorkers = false;
|
||||
AppConfig.disableWorkers = false;
|
||||
|
||||
// Teams are always loaded during the initial loading screen (for the first tab only if
|
||||
// SharedWorkers are available). Allowing users to be members of multiple teams can
|
||||
// make them have a very slow loading time. To avoid impacting the user experience
|
||||
// significantly, we're limiting the number of teams per user to 3 by default.
|
||||
// You can change this value here.
|
||||
//config.maxTeamsSlots = 5;
|
||||
//AppConfig.maxTeamsSlots = 5;
|
||||
|
||||
// Each team is considered as a registered user by the server. Users and teams are indistinguishable
|
||||
// in the database so teams will offer the same storage limits as users by default.
|
||||
|
@ -181,7 +180,7 @@ define(function() {
|
|||
// We're limiting the number of teams each user is able to own to 1 in order to make sure
|
||||
// users don't use "fake" teams (1 member) just to increase their storage limit.
|
||||
// You can change the value here.
|
||||
// config.maxOwnedTeams = 5;
|
||||
// AppConfig.maxOwnedTeams = 5;
|
||||
|
||||
// The userlist displayed in collaborative documents is stored alongside the document data.
|
||||
// Everytime someone with edit rights joins a document or modify their user data (display
|
||||
|
@ -192,14 +191,14 @@ define(function() {
|
|||
// position of other users' cursor. You can configure the number of user from which the session
|
||||
// will enter into degraded mode. A big number may result in collaborative edition being broken,
|
||||
// but this number depends on the network and CPU performances of each user's device.
|
||||
config.degradedLimit = 8;
|
||||
AppConfig.degradedLimit = 8;
|
||||
|
||||
// In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad
|
||||
// in which they could store their pads. The new "driveless" mode allow users to open an existing
|
||||
// pad without creating a drive in the background. The drive will only be created if they visit
|
||||
// a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable
|
||||
// the driveless mode by changing the following value to "false"
|
||||
config.allowDrivelessMode = true;
|
||||
AppConfig.allowDrivelessMode = true;
|
||||
|
||||
return config;
|
||||
return AppConfig;
|
||||
});
|
||||
|
|
|
@ -5355,10 +5355,12 @@ var PDFFunction = function PDFFunctionClosure() {
|
|||
var domain = IR[1];
|
||||
var range = IR[2];
|
||||
var code = IR[3];
|
||||
/*
|
||||
var compiled = new PostScriptCompiler().compile(code, domain, range);
|
||||
if (compiled) {
|
||||
return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
|
||||
}
|
||||
*/
|
||||
(0, _util.info)('Unable to compile PS function');
|
||||
var numOutputs = range.length >> 1;
|
||||
var numInputs = domain.length >> 1;
|
||||
|
@ -38545,4 +38547,4 @@ if (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked) {
|
|||
/***/ })
|
||||
/******/ ]);
|
||||
});
|
||||
//# sourceMappingURL=pdf.worker.js.map
|
||||
//# sourceMappingURL=pdf.worker.js.map
|
||||
|
|
|
@ -448,6 +448,9 @@ define([
|
|||
}
|
||||
};
|
||||
funcs.createPad = function (cfg, cb) {
|
||||
if (AppConfig.disableAnonymousPadCreation && !funcs.isLoggedIn()) {
|
||||
return void UI.errorLoadingScreen(Messages.mustLogin);
|
||||
}
|
||||
ctx.sframeChan.query("Q_CREATE_PAD", {
|
||||
owned: cfg.owned,
|
||||
expire: cfg.expire,
|
||||
|
|
|
@ -9,14 +9,17 @@ define([
|
|||
var end = cfg.endpicker;
|
||||
|
||||
var is24h = false
|
||||
var dateFormat = "Y-m-d H:i";
|
||||
try {
|
||||
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
|
||||
} catch (e) {}
|
||||
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
|
||||
|
||||
var e = $(end.input)[0];
|
||||
var endPickr = Flatpickr(e, {
|
||||
enableTime: true,
|
||||
time_24hr: is24h,
|
||||
dateFormat: dateFormat,
|
||||
minDate: start.date
|
||||
});
|
||||
endPickr.setDate(end.date);
|
||||
|
@ -25,6 +28,7 @@ define([
|
|||
var startPickr = Flatpickr(s, {
|
||||
enableTime: true,
|
||||
time_24hr: is24h,
|
||||
dateFormat: dateFormat,
|
||||
onChange: function () {
|
||||
endPickr.set('minDate', startPickr.parseDate(s.value));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue