Merge branch 'soon' into block-embeds

pull/1/head
ansuz 3 years ago
commit e38e08fb6e

@ -135,7 +135,7 @@ Our team has limited resources, so we've chosen to introduce the new (and **expe
To enable the use of the OnlyOffice Document and Presentation editor for everyone on your instance, edit your [customize/application_config.js](https://docs.cryptpad.fr/en/admin_guide/customization.html#application-config) file to include `AppConfig.enableEarlyAccess = true;`.
If you wish to avoid a rush of support tickets from your users by limiting early access to users with custom quota increases, add another line like so `Constants.earlyAccessApps = ['doc', 'presentation'];`.
If you wish to avoid a rush of support tickets from your users by limiting early access to users with custom quota increases, add another line like so `AppConfig.premiumTypes = ['doc', 'presentation'];`.
As these editors become more stable we plan to enable them by default on third-party instances. Keep in mind, these editors may be unstable and users may lose their work. Our team will fix bugs given sufficient information to reproduce them, but we will not take the time to help you recover lost data unless you have taken a support contract with us.

@ -73,12 +73,13 @@ define([
return select;
};
var footerCol = function (title, L, literal) {
return h('div.col-sm-3', [
var footerCol = function (title, L, n) {
n = n || 3;
return h('div.col-sm-' + n, [
h('ul.list-unstyled', [
h('li.footer-title', {
'data-localization': title,
}, title? Msg[title]: literal )
}, Msg[title])
].concat(L.map(function (l) {
return h('li', [ l ]);
}))
@ -102,36 +103,63 @@ define([
return h('a', attrs, text);
};
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint);
Pages.versionString = "v4.13.0";
var customURLs = Pages.customURLs = {};
(function () {
var defaultURLs = {
//imprint: '/imprint.html',
//privacy: '/privacy.html',
terms: '/terms.html',
//roadmap: '/roadmap.html',
source: 'https://github.com/xwiki-labs/cryptpad',
};
var l = Msg._getLanguage();
['imprint', 'privacy', 'terms', 'roadmap', 'source'].forEach(function (k) {
var value = AppConfig[k];
if (value === false) { return; }
if (value === true) {
customURLs[k] = defaultURLs[k];
return;
}
if (!value) { return; }
if (typeof(value) === 'string') {
customURLs[k] = value;
return;
}
if (typeof(value) === 'object') {
customURLs[k] = value[l] || value['default'];
}
});
}());
// used for the about menu
Pages.imprintLink = AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined;
Pages.privacyLink = footLink(AppConfig.privacy, 'privacy');
Pages.githubLink = footLink('https://github.com/xwiki-labs/cryptpad', null, 'GitHub');
Pages.imprintLink = footLink(customURLs.imprint, 'imprint');
Pages.privacyLink = footLink(customURLs.privacy, 'privacy');
Pages.termsLink = footLink(customURLs.terms, 'footer_tos');
Pages.sourceLink = footLink(customURLs.source, 'footer_source');
Pages.docsLink = footLink('https://docs.cryptpad.fr', 'docs_link');
Pages.roadmapLink = footLink(AppConfig.roadmap, 'footer_roadmap');
Pages.roadmapLink = footLink(customURLs.roadmap, 'footer_roadmap');
Pages.infopageFooter = function () {
var terms = footLink('/terms.html', 'footer_tos'); // FIXME this should be configurable like the other legal pages
var legalFooter;
// only display the legal part of the footer if it has content
if (terms || Pages.privacyLink || Pages.imprintLink) {
if (Pages.termsLink || Pages.privacyLink || Pages.imprintLink) {
legalFooter = footerCol('footer_legal', [
terms,
Pages.termsLink,
Pages.privacyLink,
Pages.imprintLink,
]);
}
var n = legalFooter ? 3: 4;
return h('footer', [
h('div.container', [
h('div.row', [
h('div.col-sm-3', [
h('div.col-sm-' + n, [
h('div.cp-logo-foot', [
h('img', {
src: '/customize/CryptPad_logo.svg',
@ -140,21 +168,21 @@ define([
}),
h('span.logo-font', 'CryptPad')
])
], ''),
]),
footerCol('footer_product', [
footLink('/what-is-cryptpad.html', 'topbar_whatIsCryptpad'),
Pages.docsLink,
footLink('/features.html', Pages.areSubscriptionsAllowed()? 'pricing': 'features'), // Messages.pricing, Messages.features
Pages.githubLink,
Pages.sourceLink,
footLink('https://opencollective.com/cryptpad/contribute/', 'footer_donate'),
]),
], n),
footerCol('footer_aboutUs', [
footLink('https://blog.cryptpad.fr/', 'blog'),
footLink('/contact.html', 'contact'),
footLink('https://github.com/xwiki-labs/cryptpad/wiki/Contributors', 'footer_team'),
footLink('http://www.xwiki.com', null, 'XWiki SAS'),
Pages.roadmapLink,
]),
], n),
legalFooter,
])
]),

@ -10,13 +10,14 @@ define([
var urlArgs = Config.requireConf.urlArgs;
var tos = $(UI.createCheckbox('accept-terms')).find('.cp-checkmark-label').append(Msg.register_acceptTerms).parent()[0];
var termsLink = Pages.customURLs.terms;
$(tos).find('a').attr({
href: '/terms.html',
href: termsLink, // '/terms.html',
target: '_blank',
tabindex: '-1',
});
var frame = function (content) {
return [
h('div#cp-main', [
@ -38,6 +39,11 @@ define([
]);
}
var termsCheck;
if (termsLink) {
termsCheck = h('div.checkbox-container', tos);
}
return frame([
h('div.row.cp-register-det', [
h('div#data.hidden.col-md-6', [
@ -72,9 +78,7 @@ define([
h('div.checkbox-container', [
UI.createCheckbox('import-recent', Msg.register_importRecent, true)
]),
h('div.checkbox-container', [
tos,
]),
termsCheck,
h('button#register', Msg.login_register)
])
]),

@ -4,6 +4,8 @@ const NetfluxSrv = require('chainpad-server');
const Decrees = require("./decrees");
const nThen = require("nthen");
const Fs = require("fs");
const Path = require("path");
module.exports.create = function (Env) {
var log = Env.Log;
@ -19,6 +21,9 @@ nThen(function (w) {
console.error(err);
}
}));
}).nThen(function (w) {
var fullPath = Path.join(Env.paths.block, 'placeholder.txt');
Fs.writeFile(fullPath, 'PLACEHOLDER\n', w());
}).nThen(function () {
// asynchronously create a historyKeeper and RPC together
require('./historyKeeper.js').create(Env, function (err, historyKeeper) {

@ -155,7 +155,17 @@ var flushCache = function (Env, Server, cb) {
// CryptPad_AsyncStore.rpc.send('ADMIN', ['ARCHIVE_DOCUMENT', documentID], console.log)
var archiveDocument = function (Env, Server, cb, data) {
var id = Array.isArray(data) && data[1];
if (!Array.isArray(data)) { return void cb("EINVAL"); }
var args = data[1];
var id, reason;
if (typeof(args) === 'string') {
id = args;
} else if (args && typeof(args) === 'object') {
id = args.id;
reason = args.reason;
}
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
switch (id.length) {
@ -164,6 +174,7 @@ var archiveDocument = function (Env, Server, cb, data) {
return void Env.msgStore.archiveChannel(id, Util.both(cb, function (err) {
Env.Log.info("ARCHIVAL_CHANNEL_BY_ADMIN_RPC", {
channelId: id,
reason: reason,
status: err? String(err): "SUCCESS",
});
}));
@ -171,6 +182,7 @@ var archiveDocument = function (Env, Server, cb, data) {
return void Env.blobStore.archive.blob(id, Util.both(cb, function (err) {
Env.Log.info("ARCHIVAL_BLOB_BY_ADMIN_RPC", {
id: id,
reason: reason,
status: err? String(err): "SUCCESS",
});
}));
@ -184,7 +196,17 @@ var archiveDocument = function (Env, Server, cb, data) {
};
var restoreArchivedDocument = function (Env, Server, cb, data) {
var id = Array.isArray(data) && data[1];
if (!Array.isArray(data)) { return void cb("EINVAL"); }
var args = data[1];
var id, reason;
if (typeof(args) === 'string') {
id = args;
} else if (args && typeof(args) === 'object') {
id = args.id;
reason = args.reason;
}
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
switch (id.length) {
@ -192,6 +214,7 @@ var restoreArchivedDocument = function (Env, Server, cb, data) {
return void Env.msgStore.restoreArchivedChannel(id, Util.both(cb, function (err) {
Env.Log.info("RESTORATION_CHANNEL_BY_ADMIN_RPC", {
id: id,
reason: reason,
status: err? String(err): 'SUCCESS',
});
}));
@ -201,6 +224,7 @@ var restoreArchivedDocument = function (Env, Server, cb, data) {
return void Env.blobStore.restore.blob(id, Util.both(cb, function (err) {
Env.Log.info("RESTORATION_BLOB_BY_ADMIN_RPC", {
id: id,
reason: reason,
status: err? String(err): 'SUCCESS',
});
}));
@ -316,6 +340,9 @@ var instanceStatus = function (Env, Server, cb) {
disableIntegratedEviction: Env.disableIntegratedEviction,
disableIntegratedTasks: Env.disableIntegratedTasks,
enableProfiling: Env.enableProfiling,
profilingWindow: Env.profilingWindow,
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
@ -328,6 +355,10 @@ var instanceStatus = function (Env, Server, cb) {
updateAvailable: Env.updateAvailable,
instancePurpose: Env.instancePurpose,
instanceDescription: Env.instanceDescription,
instanceJurisdiction: Env.instanceJurisdiction,
instanceName: Env.instanceName,
});
};

@ -24,6 +24,8 @@ SET_PREMIUM_UPLOAD_SIZE
// BACKGROUND PROCESSES
DISABLE_INTEGRATED_TASKS
DISABLE_INTEGRATED_EVICTION
ENABLE_PROFILING
SET_PROFILING_WINDOW
// BROADCAST
SET_LAST_BROADCAST_HASH
@ -41,6 +43,9 @@ PROVIDE_AGGREGATE_STATISTICS
REMOVE_DONATE_BUTTON
BLOCK_DAILY_CHECK
SET_INSTANCE_JURISDICTION
SET_INSTANCE_DESCRIPTION
NOT IMPLEMENTED:
// RESTRICTED REGISTRATION
@ -148,6 +153,16 @@ var makeIntegerSetter = function (attr) {
return makeGenericSetter(attr, args_isInteger);
};
var arg_isPositiveInteger = function (args) {
return Array.isArray(args) && isInteger(args[0]) && args[0] > 0;
};
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['ENABLE_PROFILING', [true]]], console.log)
commands.ENABLE_PROFILING = makeBooleanSetter('enableProfiling');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_PROFILING_WINDOW', [10000]]], console.log)
commands.SET_PROFILING_WINDOW = makeGenericSetter('profilingWindow', arg_isPositiveInteger);
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAX_UPLOAD_SIZE', [50 * 1024 * 1024]]], console.log)
commands.SET_MAX_UPLOAD_SIZE = makeIntegerSetter('maxUploadSize');
@ -181,6 +196,15 @@ commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (arg
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_JURISDICTION', ['France']]], console.log)
commands.SET_INSTANCE_JURISDICTION = makeGenericSetter('instanceJurisdiction', args_isString);
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_NAME', ['My Personal CryptPad']]], console.log)
commands.SET_INSTANCE_NAME = makeGenericSetter('instanceDescription', args_isString);
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_DESCRIPTION', ['A personal instance, hosted for me and nobody else']]], console.log)
commands.SET_INSTANCE_DESCRIPTION = makeGenericSetter('instanceDescription', args_isString);
// Maintenance: Empty string or an object with a start and end time
var isNumber = function (value) {
return typeof(value) === "number" && !isNaN(value);

@ -66,6 +66,10 @@ module.exports.create = function (config) {
launchTime: +new Date(),
enableProfiling: false,
profilingWindow: 10000,
bytesWritten: 0,
inactiveTime: config.inactiveTime,
archiveRetentionTime: config.archiveRetentionTime,
accountRetentionTime: config.accountRetentionTime,
@ -132,6 +136,10 @@ module.exports.create = function (config) {
provideAggregateStatistics: false,
updateAvailable: undefined,
instanceName: '',
instanceDescription: '',
instanceJurisdiction: '',
myDomain: config.myDomain,
mySubdomain: config.mySubdomain, // only exists for the accounts integration
customLimits: {},
@ -233,6 +241,15 @@ module.exports.create = function (config) {
return typeof(config[key]) === 'string'? config[key]: def;
};
Env.incrementBytesWritten = function (n) {
if (!Env.enableProfiling) { return; }
if (!n || typeof(n) !== 'number' || n < 0) { return; }
Env.bytesWritten += n;
setTimeout(function () {
Env.bytesWritten -= n;
}, Env.profilingWindow);
};
paths.pin = keyOrDefaultString('pinPath', './pins');
paths.block = keyOrDefaultString('blockPath', './block');
paths.data = keyOrDefaultString('filePath', './datastore');

@ -380,10 +380,14 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash, cb)
// Message stored, call back
cb();
index.size += msgBin.length;
var msgLength = msgBin.length;
index.size += msgLength;
// handle the next element in the queue
next();
// keep track of how many bytes are written
Env.incrementBytesWritten(msgLength);
}));
});
});

@ -1,6 +1,11 @@
/*jshint esversion: 6 */
const Stats = module.exports;
var truthyStringOrNothing = function (s) {
if (typeof(s) !== 'string' || !s) { return undefined; }
return s.trim() || undefined;
};
Stats.instanceData = function (Env) {
var data = {
version: Env.version,
@ -55,6 +60,15 @@ Stats.instanceData = function (Env) {
// how long do you retain inactive accounts?
data.accountRetentionTime = Env.accountRetentionTime;
// does this instance have a name???
data.instanceName = truthyStringOrNothing(Env.instanceName);
// does this instance have a jurisdiction ???
data.instanceJurisdiction = truthyStringOrNothing(Env.instanceJurisdiction);
// does this instance have a description???
data.instanceDescription = truthyStringOrNothing(Env.instanceDescription);
// how long do you retain archived data?
//data.archiveRetentionTime = Env.archiveRetentionTime,
}

@ -131,11 +131,13 @@ var upload = function (Env, safeKey, content, cb) {
blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
});
} else {
session.blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
}
};
@ -493,6 +495,10 @@ BlobStore.create = function (config, _cb) {
Fse.mkdirp(Path.join(Env.archivePath, Env.blobPath), w(function (e) {
if (e) { CB(e); }
}));
}).nThen(function (w) {
// XXX make a placeholder file in the root of the blob path
var fullPath = Path.join(Env.blobPath, 'placeholder.txt');
Fse.writeFile(fullPath, 'PLACEHOLDER\n', w());
}).nThen(function () {
var methods = {
isFileId: isValidId,

@ -49,6 +49,7 @@ Block.archive = function (Env, publicKey, _cb) {
return void cb('E_INVALID_BLOCK_ARCHIVAL_PATH');
}
// TODO Env.incrementBytesWritten
Fse.move(currentPath, archivePath, {
overwrite: true,
}, cb);
@ -83,6 +84,7 @@ Block.write = function (Env, publicKey, buffer, _cb) {
}));
}).nThen(function () {
Fs.writeFile(path, buffer, { encoding: 'binary' }, cb);
Env.incrementBytesWritten(buffer && buffer.length);
});
};

@ -78,8 +78,8 @@ var processLang = function (map, lang, primary) {
});
var weirdCapitalization;
s.replace(/cryptpad(\.fr)*/gi, function (brand) {
if (['CryptPad', 'cryptpad.fr'].includes(brand)) { return; }
s.replace(/cryptpad(\.fr|\.org)*/gi, function (brand) {
if (['CryptPad', 'cryptpad.fr', 'cryptpad.org'].includes(brand)) { return; }
weirdCapitalization = true;
});

@ -276,6 +276,13 @@ var send404 = function (res, path) {
send404(res);
});
};
app.get('/api/profiling', function (req, res, next) {
if (!Env.enableProfiling) { return void send404(res); }
res.setHeader('Content-Type', 'text/javascript');
res.send(JSON.stringify({
bytesWritten: Env.bytesWritten,
}));
});
app.use(function (req, res, next) {
res.status(404);

@ -21,6 +21,11 @@
text-decoration: underline;
}
.alert.alert-info.cp-admin-bigger-alert {
font-size: 16px;
}
.cp-admin-setlimit-form, .cp-admin-broadcast-form {
label {
font-weight: normal !important;
@ -33,8 +38,12 @@
margin-top: 5px;
}
}
.cp-admin-setlimit-form + button {
margin-top: 5px !important;
.cp-admin-setlimit-form,
.cp-admin-setjurisdiction-form,
.cp-admin-setdescription-form {
+ button {
margin-top: 5px !important;
}
}
.cp-admin-getlimits {
code {
@ -199,6 +208,12 @@
input.cp-admin-inval {
border-color: red !important;
}
input[type="text"], textarea {
&.cp-listing-info[disabled] {
border: 1px solid transparent !important; //1px solid transparent !imprortant;;//none !important;
}
}
.cp-admin-nopassword {
.cp-admin-pw {
display: none !important;

@ -53,20 +53,27 @@ define([
'general': [ // Msg.admin_cat_general
'cp-admin-flush-cache',
'cp-admin-update-limit',
'cp-admin-archive',
'cp-admin-unarchive',
'cp-admin-registration',
'cp-admin-disableembeds',
'cp-admin-email'
'cp-admin-email',
'cp-admin-instance-info-notice',
'cp-admin-name',
'cp-admin-description',
'cp-admin-jurisdiction',
],
'quota': [ // Msg.admin_cat_quota
'cp-admin-defaultlimit',
'cp-admin-setlimit',
'cp-admin-archive',
'cp-admin-unarchive',
'cp-admin-getquota',
'cp-admin-getlimits',
],
'stats': [ // Msg.admin_cat_stats
'cp-admin-refresh-stats',
'cp-admin-uptime',
'cp-admin-active-sessions',
'cp-admin-active-pads',
'cp-admin-open-files',
@ -86,6 +93,8 @@ define([
'performance': [ // Msg.admin_cat_performance
'cp-admin-refresh-performance',
'cp-admin-performance-profiling',
'cp-admin-enable-disk-measurements',
'cp-admin-bytes-written',
],
'network': [ // Msg.admin_cat_network
'cp-admin-update-available',
@ -93,6 +102,7 @@ define([
'cp-admin-block-daily-check',
//'cp-admin-provide-aggregate-statistics',
'cp-admin-list-my-instance',
'cp-admin-consent-to-contact',
'cp-admin-remove-donate-button',
'cp-admin-instance-purpose',
@ -160,6 +170,13 @@ define([
id: 'cp-admin-archive-pw',
placeholder: Messages.login_password
});
var input3 = h('input', {
id: 'cp-admin-archive-reason',
});
var label3 = h('label', {
for: 'cp-admin-archive-reason',
}, Messages.admin_archiveNote);
var $pw = $(input2);
$pw.addClass('cp-admin-pw');
var $pwInput = $pw.find('input');
@ -169,7 +186,9 @@ define([
label,
input,
label2,
input2
input2,
label3,
input3,
]));
$div.addClass('cp-admin-nopassword');
@ -235,9 +254,13 @@ define([
}
}), true);
}).nThen(function () {
var $reason = $(input3);
sFrameChan.query('Q_ADMIN_RPC', {
cmd: archive ? 'ARCHIVE_DOCUMENT' : 'RESTORE_ARCHIVED_DOCUMENT',
data: channel
data: {
id: channel,
reason: $reason.val(),
},
}, function (err, obj) {
var e = err || (obj && obj.error);
clicked = false;
@ -249,6 +272,8 @@ define([
UI.log(archive ? Messages.archivedFromServer : Messages.restoredFromServer);
$input.val('');
$pwInput.val('');
// disabled because it's actually pretty annoying to re-enter this each time if you are archiving many files
//$reason.val('');
});
});
});
@ -393,10 +418,11 @@ define([
},
});
// XXX remove emailButton
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 $div = makeBlock(key, true); // Msg.admin_emailHint, Msg.admin_emailTitle
var $button = $div.find('button').text(Messages.settings_save);
var input = h('input', {
type: 'email',
@ -432,6 +458,142 @@ define([
return $div;
};
create['jurisdiction'] = function () {
var key = 'jurisdiction';
var $div = makeBlock(key, true); // Msg.admin_jurisdictionHint, Msg.admin_jurisdictionTitle, Msg.admin_jurisdictionButton
var $button = $div.find('button').addClass('cp-listing-action').text(Messages.settings_save);
var input = h('input.cp-listing-info', {
type: 'text',
value: APP.instanceStatus.instanceJurisdiction || '',
placeholder: Messages.admin_jurisdictionPlaceholder,
});
var $input = $(input);
var innerDiv = h('div.cp-admin-setjurisdiction-form', input);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_INSTANCE_JURISDICTION', [$input.val().trim()]]
}, 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._getKey('ui_saved', [Messages.admin_jurisdictionTitle]));
});
});
$button.before(innerDiv);
return $div;
};
Messages.admin_infoNotice1 = "The following fields describe your instance. Data entered will only be included in your server's telemetry if you opt in to inclusion in the list of public CryptPad instances."; // XXX
Messages.admin_infoNotice2 = "See the 'Network' tab for more details."; // XXX
create['instance-info-notice'] = function () {
return $(h('div.cp-admin-instance-info-notice.cp-sidebarlayout-element',
h('div.alert.alert-info.cp-admin-bigger-alert', [
Messages.admin_infoNotice1,
' ',
Messages.admin_infoNotice2,
])
));
};
create['name'] = function () {
var key = 'name';
var $div = makeBlock(key, true);
// Msg.admin_nameHint, Msg.admin_nameTitle, Msg.admin_nameButton
var $button = $div.find('button').addClass('cp-listing-action').text(Messages.settings_save);
var input = h('input.cp-listing-info', {
type: 'text',
value: APP.instanceStatus.instanceName || ApiConfig.httpUnsafeOrigin || '',
placeholder: ApiConfig.httpUnsafeOrigin,
style: 'margin-bottom: 5px;',
});
var $input = $(input);
var innerDiv = h('div.cp-admin-setname-form', input);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_INSTANCE_NAME', [$input.val().trim()]]
}, 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._getKey('ui_saved', [Messages.admin_nameTitle]));
});
});
$button.before(innerDiv);
return $div;
};
create['description'] = function () {
var key = 'description';
var $div = makeBlock(key, true); // Msg.admin_descriptionHint
var textarea = h('textarea.cp-admin-description-text.cp-listing-info', {
placeholder: Messages.home_host || '',
}, APP.instanceStatus.instanceDescription || '');
var $button = $div.find('button').text(Messages.settings_save);
$button.addClass('cp-listing-action');
var innerDiv = h('div.cp-admin-setdescription-form', [
textarea,
]);
$button.before(innerDiv);
var $input = $(textarea);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_INSTANCE_DESCRIPTION', [$input.val().trim()]]
}, 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._getKey('ui_saved', [Messages.admin_descriptionTitle]));
});
});
return $div;
};
var getPrettySize = UIElements.prettySize;
create['defaultlimit'] = function () {
@ -559,11 +721,11 @@ define([
note,
h('nav', [set, remove])
]);
var $note = $(note);
var getValues = function () {
var key = $key.val();
var _limit = parseInt($(limit).val());
var _note = $(note).val();
if (key.length !== 44) {
try {
var u = Keys.parseUser(key);
@ -577,6 +739,7 @@ define([
if (isNaN(_limit) || _limit < 0) {
return void UI.warn(Messages.admin_invalLimit);
}
var _note = ($note.val() || "").trim();
return {
key: key,
data: {
@ -627,6 +790,7 @@ define([
}
APP.refreshLimits();
$key.val('');
$note.val('');
});
});
@ -684,6 +848,29 @@ define([
return $div;
};
Messages.admin_uptimeTitle = 'Launch time';
Messages.admin_uptimeHint = 'Date and time at which the server was launched';
create['uptime'] = function () {
var key = 'uptime';
var $div = makeBlock(key); // Msg.admin_activeSessionsHint, .admin_activeSessionsTitle
var pre = h('pre');
var set = function () {
var uptime = APP.instanceStatus.launchTime;
if (typeof(uptime) !== 'number') { return; }
pre.innerText = new Date(uptime);
};
set();
$div.append(pre);
onRefreshStats.reg(function () {
set();
});
return $div;
};
create['active-sessions'] = function () {
var key = 'active-sessions';
var $div = makeBlock(key); // Msg.admin_activeSessionsHint, .admin_activeSessionsTitle
@ -950,24 +1137,30 @@ define([
};
var _reorder = function () {
var orderAnswered = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && d.lastAdmin && !d.closed;
}).sort(sort);
var orderPremium = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && d.premium && !d.lastAdmin && !d.closed;
}).sort(sort);
var orderNormal = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && !d.premium && !d.lastAdmin && !d.closed;
}).sort(sort);
var orderClosed = Object.keys(hashesById).filter(function (id) {
var orderAnswered = [];
var orderPremium = [];
var orderNormal = [];
var orderClosed = [];
Object.keys(hashesById).forEach(function (id) {
var d = getTicketData(id);
return d && d.closed;
}).sort(sort);
if (!d) { return; }
if (d.closed) {
return void orderClosed.push(id);
}
if (d.lastAdmin /* && !d.closed */) {
return void orderAnswered.push(id);
}
if (d.premium /* && !d.lastAdmin && !d.closed */) {
return void orderPremium.push(id);
}
orderNormal.push(id);
//if (!d.premium && !d.lastAdmin && !d.closed) { return void orderNormal.push(id); }
});
var cols = [$col1, $col2, $col3, $col4];
[orderPremium, orderNormal, orderAnswered, orderClosed].forEach(function (list, j) {
list.sort(sort);
list.forEach(function (id, i) {
var $t = $div.find('[data-id="'+id+'"]');
var d = getTicketData(id);
@ -1779,6 +1972,84 @@ define([
return $div;
};
Messages.admin_enableDiskMeasurementsTitle = "Measure disk performance"; // XXX
Messages.admin_enableDiskMeasurementsHint = "If enabled, a JSON endpoint will be exposed under /api/profiling which keeps a running measurement of disk I/O within a configurable window (set below). This setting can impact server performance and may reveal data you'd rather keep hidden. It is recommended that you leave it disabled unless you know what you are doing."; // XXX
create['enable-disk-measurements'] = makeAdminCheckbox({ // Msg.admin_enableDiskMeasurementsTitle.admin_enableDiskMeasurementsHint
key: 'enable-disk-measurements',
getState: function () {
return APP.instanceStatus.enableProfiling;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['ENABLE_PROFILING', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.enableProfiling);
});
});
},
});
Messages.admin_bytesWrittenTitle = "Disk performance measurement window"; // XXX
Messages.admin_bytesWrittenHint = "If you have enabled disk performance measurements then the duration of the window can be configured below."; // XXX
Messages.admin_bytesWrittenDuration = "Duration of the window in milliseconds: {0}"; // XXX
//Messages.admin_defaultDuration = "admin_defaultDuration"; // XXX
Messages.admin_setDuration = "Set duration"; // XXX
var isPositiveInteger = function (n) {
return n && typeof(n) === 'number' && n % 1 === 0 && n > 0;
};
create['bytes-written'] = function () {
var key = 'bytes-written';
var $div = makeBlock(key);
var duration = APP.instanceStatus.profilingWindow;
if (!isPositiveInteger(duration)) { duration = 10000; }
var newDuration = h('input', {type: 'number', min: 0, value: duration});
var set = h('button.btn.btn-primary', Messages.admin_setDuration);
$div.append(h('div', [
h('span.cp-admin-bytes-written-duration', Messages._getKey('admin_bytesWrittenDuration', [duration])),
h('div.cp-admin-setlimit-form', [
newDuration,
h('nav', [set])
])
]));
UI.confirmButton(set, {
classes: 'btn-primary',
multiple: true,
validate: function () {
var l = parseInt($(newDuration).val());
if (isNaN(l)) { return false; }
return true;
}
}, function () {
var d = parseInt($(newDuration).val());
if (!isPositiveInteger(d)) { return void UI.warn(Messages.error); }
var data = [d];
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_PROFILING_WINDOW', data]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
return void console.error(e, response);
}
$div.find('.cp-admin-bytes-written-duration').text(Messages._getKey('admin_bytesWrittenDuration', [d]));
});
});
return $div;
};
create['update-available'] = function () { // Messages.admin_updateAvailableTitle.admin_updateAvailableHint.admin_updateAvailableLabel.admin_updateAvailableButton
if (!APP.instanceStatus.updateAvailable) { return; }
var $div = makeBlock('update-available', true);
@ -2059,6 +2330,20 @@ define([
if (!Array.isArray(data)) { return void cb('EINVAL'); }
APP.instanceStatus = data[0];
console.log("Status", APP.instanceStatus);
/*
var isListed = Boolean(APP.instanceStatus.listMyInstance);
var $actions = $('.cp-listing-action');
var $fields = $('.cp-listing-info');
if (isListed) {
$actions.removeAttr('disabled');
$fields.removeAttr('disabled');
} else {
$actions.attr('disabled', 'disabled');
$fields.attr('disabled', 'disabled');
}
*/
cb();
});
};

@ -392,7 +392,6 @@ define([
assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var url = cacheBuster(sheetURL);
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
@ -1137,6 +1136,141 @@ define([
});
});
var POLICY_ADVISORY = " This link will be included in the home page footer and 'About CryptPad' menu. It's advised that you either provide one or disable registration.";
var APPCONFIG_DOCS_LINK = function (key) {
return h('span', [
" See ",
h('a', {
href: 'https://docs.cryptpad.fr/en/admin_guide/customization.html#application-config',
target: "_blank",
rel: 'noopener noreferrer',
}, "the relevant documentation"),
" about how to customize CryptPad's ",
code(key),
' value.',
]);
};
var isValidInfoURL = function (url) {
if (!url || typeof(url) !== 'string') { return false; }
try {
var parsed = new URL(url, ApiConfig.httpUnsafeOrigin);
// check that the URL parsed and that they haven't simply linked to
// '/' or '.' or something silly like that.
return ![
ApiConfig.httpUnsafeOrigin,
ApiConfig.httpUnsafeOrigin + '/',
].includes(parsed.href);
} catch (err) {
return false;
}
};
// check if they provide terms of service
assert(function (cb, msg) {
if (ApiConfig.restrictRegistration) { return void cb(true); }
var url = Pages.customURLs.terms;
setWarningClass(msg);
msg.appendChild(h('span', [
'No terms of service were specified.',
POLICY_ADVISORY,
APPCONFIG_DOCS_LINK('terms'),
]));
cb(isValidInfoURL(url) || url);
});
// check if they provide legal data
assert(function (cb, msg) {
if (true) { return void cb(true); } // XXX stubbed while we determine whether this is necessary
if (ApiConfig.restrictRegistration) { return void cb(true); }
var url = Pages.customURLs.imprint;
setWarningClass(msg);
msg.appendChild(h('span', [
'No legal entity data was specified.',
POLICY_ADVISORY,
APPCONFIG_DOCS_LINK('imprint'),
]));
cb(isValidInfoURL(url) || url);
});
// check if they provide a privacy policy
assert(function (cb, msg) {
if (ApiConfig.restrictRegistration) { return void cb(true); }
var url = Pages.customURLs.privacy;
setWarningClass(msg);
msg.appendChild(h('span', [
'No privacy policy was specified.',
POLICY_ADVISORY,
APPCONFIG_DOCS_LINK('privacy'),
]));
cb(isValidInfoURL(url) || url);
});
// check if they provide a link to source code
assert(function (cb, msg) {
if (ApiConfig.restrictRegistration) { return void cb(true); }
var url = Pages.customURLs.source;
setWarningClass(msg);
msg.appendChild(h('span', [
'No source code link was specified.',
POLICY_ADVISORY,
APPCONFIG_DOCS_LINK('source'),
]));
cb(isValidInfoURL(url) || url);
});
assert(function (cb, msg) {
var path = '/blob/placeholder.txt';
var fullPath;
try {
fullPath = new URL(path, ApiConfig.fileHost || ApiConfig.httpUnsafeOrigin).href;
} catch (err) {
fullPath = path;
}
msg.appendChild(h('span', [
"A placeholder file was expected to be available at ",
code(fullPath),
", but it was not found.",
" This commonly indicates a mismatch between the API server's ",
code('blobPath'),
" value and the path that the webserver or reverse proxy is attempting to serve.",
" This misconfiguration will cause errors with uploaded files and CryptPad's office editors (sheet, presentation, document).",
]));
Tools.common_xhr(fullPath, xhr => {
cb(xhr.status === 200 || xhr.status);
});
});
assert(function (cb, msg) {
var path = '/block/placeholder.txt';
var fullPath;
try {
fullPath = new URL(path, ApiConfig.fileHost || ApiConfig.httpUnsafeOrigin).href;
} catch (err) {
fullPath = path;
}
msg.appendChild(h('span', [
"A placeholder file was expected to be available at ",
code(fullPath),
", but it was not found.",
" This commonly indicates a mismatch between the API server's ",
code('blockPath'),
" value and the path that the webserver or reverse proxy is attempting to serve.",
" This misconfiguration will cause errors with login, registration, and password change.",
]));
Tools.common_xhr(fullPath, xhr => {
cb(xhr.status === 200 || xhr.status);
});
});
var serverToken;
Tools.common_xhr('/', function (xhr) {
serverToken = xhr.getResponseHeader('server');
@ -1226,9 +1360,20 @@ define([
details,
]);
var isWarning = function (x) {
return x && /cp\-warning/.test(x.getAttribute('class'));
};
var sortMethod = function (a, b) {
if (isWarning(a.message) && !isWarning(b.message)) {
return 1;
}
return a.test - b.test;
};
var report = h('div.report', [
summary,
h('div.failures', errors.map(failureReport)),
h('div.failures', errors.sort(sortMethod).map(failureReport)),
]);
$progress.remove();

@ -35,6 +35,9 @@ define(function() {
//'doc', 'presentation'
];
// XXX
// AppConfig.premiumTypes = ['doc', 'presentation'];
/* 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
* will use the english version instead. You can customize the langauges you want to be available
@ -45,25 +48,64 @@ define(function() {
*/
//AppConfig.availableLanguages = ['en', 'fr', 'de'];
/*
* AppConfig.imprint, AppConfig.privacy, AppConfig.terms, AppConfig.source, and AppConfig.roadmap
* define values used in at least one of the static pages' footer or the 'About CryptPad' menu.
*
* They can each be configured in one of three manners:
*
* 1. set their value to `false` to cause them not to be displayed, even if a default value exists
* example:
* AppConfig.privacy = false;
* 2. set their value to `true` to use the default value if it exists.
* example:
* AppConfig.privacy = true;
* 3. set their value to an object which maps language codes or a default setting to the relevant URL (as a string)
* example:
* AppConfig.privacy = {
* "default": 'https://example.com/privacy.html',
* "en": 'https://example.com/privacy.en.html', // in case English is not your default language
* "fr": 'https://example.com/privacy.fr.html', // another language
* "de": 'https://example.com/privacy.de.html', // you get the idea?
* };
*
*/
/* 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.
* footer. Since this is different for each individual or organization there is
* no default value.
*
* See the comments above for a description of possible configurations.
*/
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.
* Since this is different for each individual or organization there is no default value.
* See the comments above for a description of possible configurations.
*/
AppConfig.privacy = false;
/* You can display a link to your instances's terms of service in the static pages footer.
* A default is included for backwards compatibility, but we recommend replacing this
* with your own terms.
*
* See the comments above for a description of possible configurations.
*/
AppConfig.terms = true;
/* The terms of CryptPad's license require that its source code be made available
* to anyone who uses the software. If you have not made any modifications to the platform
* then it is sufficient to leave this as-is. If you have made changes, customize
* this value to a software repository which includes the source code including your modifications.
*
* See the comments above for a description of possible configurations.
*/
// AppConfig.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
AppConfig.source = true;
/* 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.
/* If you wish to communicate your organization's roadmap to your users you may use the setting below.
* Since this is different for each individual or organization there is no default value.
*/
//AppConfig.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
AppConfig.roadmap = false;
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds

@ -86,11 +86,11 @@ define([
}
} catch (e) { console.error(e); failStore(); }
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
if (typeof(Promise) !== 'function') {
setTimeout(function () {
return void setTimeout(function () {
var s = "Internet Explorer is not supported anymore, including by Microsoft.\n\nMost of CryptPad's collaborative functionality requires a modern browser to work.\n\nWe recommend Mozilla Firefox.";
window.alert(s);
});
}
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
});

@ -808,7 +808,9 @@ define([
h('span.cp-toolbar-name.cp-toolbar-drawer-element', Messages.toolbar_storeInDrive)
])).click(common.prepareFeedback(type)).click(function () {
$(button).hide();
common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) {
common.getSframeChannel().query("Q_AUTOSTORE_STORE", {
forceOwnDrive: true,
}, function (err, obj) {
var error = err || (obj && obj.error);
if (error) {
$(button).show();
@ -1647,9 +1649,10 @@ define([
var template = function (line, link) {
if (!line || !link) { return; }
var p = $('<p>').html(line)[0];
var p = $('<p>').html(line)[0]; // XXX
var sub = link.cloneNode(true);
// XXX use URL if you need to?
/* This is a hack to make relative URLs point to the main domain
instead of the sandbox domain. It will break if the admins have specified
some less common URL formats for their customizable links, such as if they've
@ -1666,8 +1669,9 @@ define([
var legalLine = template(Messages.info_imprintFlavour, Pages.imprintLink);
var privacyLine = template(Messages.info_privacyFlavour, Pages.privacyLink);
var faqLine = template(Messages.help_genericMore, Pages.docsLink);
var termsLine = template(Messages.info_termsFlavour, Pages.termsLink);
var sourceLine = template(Messages.info_sourceFlavour, Pages.sourceLink);
var content = h('div.cp-info-menu-container', [
h('div.logo-block', [
@ -1678,9 +1682,11 @@ define([
h('span', Pages.versionString)
]),
h('hr'),
legalLine,
privacyLine,
faqLine,
termsLine,
privacyLine,
legalLine,
sourceLine,
]);
$(content).find('a').attr('target', '_blank');

@ -974,6 +974,9 @@ define([
data.forceSave = 1;
//delete common.initialTeam;
}
if (data.forceOwnDrive) {
data.teamId = -1;
}
if (common.initialPath) {
if (!data.path) {
data.path = Array.isArray(common.initialPath) ? common.initialPath

@ -1804,6 +1804,7 @@ define([
}
if (APP.isDownload) {
delete APP.isDownload;
var bin = getContent();
if (!supportsXLSX()) {
return void sframeChan.event('EV_OOIFRAME_DONE', bin, {raw: true});
@ -2008,6 +2009,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
var blobUrl = (typeof mediasData[data.src] === 'undefined') ? "" : mediasData[data.src].blobUrl;
if (blobUrl) {
delete downloadImages[name];
debug("CryptPad Image already loaded " + blobUrl);
return void callback(blobUrl);
}
@ -2556,7 +2558,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
type:'text/javascript',
src: '/common/onlyoffice/'+version+'web-apps/apps/api/documents/api.js'
});
$('#cp-app-oo-editor').append(s);
$('#cp-app-oo-editor').empty().append(h('div#cp-app-oo-placeholder-a')).append(s);
var hashes = content.hashes || {};
var idx = sortCpIndex(hashes);

@ -15,15 +15,17 @@ define([
var create = function (config) {
// Loaded in load #2
var sframeChan;
var Util = config.modules.Utils.Util;
var _onReadyEvt = Util.mkEvent(true);
var refresh = function (data, cb) {
if (currentCb) {
queue.push({data: data, cb: cb});
return;
}
if (!ready) {
ready = function () {
_onReadyEvt.reg(function () {
refresh(data, cb);
};
});
return;
}
currentCb = cb;
@ -152,10 +154,8 @@ define([
sframeChan.onReady(function () {
if (ready === true) { return; }
if (typeof ready === "function") {
ready();
}
ready = true;
_onReadyEvt.fire();
});
});
});

@ -1159,8 +1159,9 @@ define([
expire = data.expire;
}
var storeLocally = data.teamId === -1;
//var storeLocally = data.teamId === -1;
if (data.teamId === -1) { data.teamId = undefined; }
if (data.teamId) { data.teamId = Number(data.teamId); }
// If a teamId is provided, it means we want to store the pad in a specific
// team drive. In this case, we just need to check if the pad is already
@ -1170,11 +1171,8 @@ define([
// If it is stored, update its data, otherwise ask the user if they want to store it
var allData = [];
var sendTo = [];
var inMyDrive;
var inTargetDrive, inMyDrive;
getAllStores().forEach(function (s) {
if (data.teamId && s.id !== data.teamId) { return; }
if (storeLocally && s.id) { return; }
// If this is an edit link but we don't have edit rights, this entry is not useful
if (h.mode === "edit" && s.id && !s.secondaryKey) {
return;
@ -1185,16 +1183,15 @@ define([
sendTo.push(s.id);
}
// If we've just accepted ownership for a pad stored in a shared folder,
// we need to make a copy of this pad in our drive. We're going to check
// if the pad is stored in our MAIN drive.
// We only need to check this if the current manager is the target (data.teamId)
if (data.teamId === s.id) {
inMyDrive = res.some(function (obj) {
return !obj.fId;
});
// Check if the pad is already stored in the specified drive (data.teamId)
if ((!s.id && !data.teamId) || Number(s.id) === data.teamId) {
if (!inTargetDrive) {
inTargetDrive = res.length;
}
}
if (!s.id) { inMyDrive = res.length; }
Array.prototype.push.apply(allData, res);
});
var contains = allData.length !== 0;
@ -1231,7 +1228,7 @@ define([
});
// Add the pad if it does not exist in our drive
if (!contains || (data.forceSave && !inMyDrive)) {
if (!contains || (data.forceSave && !inTargetDrive)) {
var autoStore = Util.find(store.proxy, ['settings', 'general', 'autostore']);
if (autoStore !== 1 && !data.forceSave && !data.path) {
// send event to inner to display the corner popup
@ -1258,7 +1255,8 @@ define([
}, cb);
// Let inner know that dropped files shouldn't trigger the popup
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
stored: true
stored: true,
inMyDrive: inMyDrive || (!contains && !data.teamId) // display "store in cryptdrive" entry
});
return;
}

@ -1181,7 +1181,8 @@ define([
title: currentTitle,
channel: secret.channel,
path: initialPathInDrive, // Where to store the pad if we don't have it in our drive
forceSave: true
forceSave: true,
forceOwnDrive: obj && obj.forceOwnDrive
};
setPadTitle(data, cb);
});

@ -967,8 +967,8 @@
"slide_textCol": "Textfarbe",
"support_languagesPreamble": "Das Support-Team spricht die folgenden Sprachen:",
"settings_safeLinkDefault": "Sichere Links sind nun standardmäßig aktiviert. Bitte verwende zum Kopieren von Links das Menü <i></i> <b>Teilen</b> und nicht die Adressleiste des Browsers.",
"info_imprintFlavour": "<a>Rechtliche Informationen über die Administratoren dieses Servers</a>.",
"info_privacyFlavour": "Unsere <a>Datenschutzerklärung</a> beschreibt, wie wir deine Daten verarbeiten.",
"info_imprintFlavour": "<a>Rechtliche Informationen</a> über die Administratoren dieses Servers",
"info_privacyFlavour": "<a>Datenschutzerklärung</a> für diese Instanz",
"user_about": "Über CryptPad",
"support_cat_all": "Alle",
"support_cat_other": "Anderes",
@ -1427,5 +1427,26 @@
"form_exportSheet": "In Tabelle exportieren",
"form_answerChoice": "Bitte wähle aus, wie du dieses Formular beantworten möchtest:",
"bounce_danger": "Der Link, den du angeklickt hast, führt nicht zu einer Webseite, sondern zu einem Code oder Daten, die bösartig sein könnten.\n\n(\"{0}\")\n\nCryptPad blockiert solche Links aus Sicherheitsgründen. Klicke auf OK, um diesen Tab zu schließen.",
"bounce_confirm": "Du bist dabei zu verlassen: {0}\n\nBist du sicher, dass du \"{1}\" besuchen möchtest?"
"bounce_confirm": "Du bist dabei zu verlassen: {0}\n\nBist du sicher, dass du \"{1}\" besuchen möchtest?",
"info_sourceFlavour": "<a>Quellcode</a> von CryptPad",
"footer_source": "Quellcode",
"admin_jurisdictionHint": "Das Land, in dem die verschlüsselten Daten dieser Instanz gespeichert werden",
"admin_jurisdictionTitle": "Server-Standort",
"admin_archiveNote": "Notiz",
"support_warning_account": "Bitte beachte, dass Administratoren keine Passwörter zurücksetzen können. Wenn du die Zugangsdaten zu deinem Account verloren hast, aber noch eingeloggt bist, kannst du <a>deine Daten in einen neuen Account importieren</a>.",
"support_warning_prompt": "Bitte wähle die für dein Anliegen am besten geeignete Kategorie aus. Dies hilft den Administratoren bei der Bearbeitung und liefert weitere Vorschläge, welche Informationen bereitgestellt werden sollten.",
"support_warning_other": "Worum geht es bei deiner Anfrage? Bitte gib so viele relevante Informationen wie möglich an, damit wir dein Anliegen schnell bearbeiten können.",
"support_warning_abuse": "Bitte melde Inhalte, die gegen die <a>Nutzungsbedingungen</a> verstoßen. Bitte gib Links zu den betreffenden Dokumenten oder Nutzerprofilen an und beschreibe, wie sie gegen die Nutzungsbedingungen verstoßen. Jede zusätzliche Information über den Kontext, in dem du den Inhalt oder das Verhalten entdeckt hast, kann den Administratoren helfen, zukünftige Verstöße zu verhindern.",
"support_warning_bug": "Bitte gib an, in welchem Browser die Probleme auftreten und ob Erweiterungen installiert sind. Bitte beschreibe das Problem so detailliert wie möglich und gib die Schritte an, die zur Reproduktion des Problems notwendig sind.",
"support_warning_document": "Bitte gib an, bei welchem Dokumententyp es Probleme gibt, sowie die <a>Kennung des Dokuments</a> oder einen Link.",
"support_warning_drives": "Beachte, dass Administratoren nicht in der Lage sind, Ordner und Dokumente anhand ihres Namens zu identifizieren. Wenn es um einen geteilten Ordner geht, gib bitte die dazugehörige <a>Kennung des Dokuments</a> an.",
"info_termsFlavour": "<a>Nutzungsbedingungen</a> für diese Instanz",
"admin_descriptionHint": "Die Beschreibung wird in der Liste öffentlicher Instanzen auf cryptpad.org angezeigt",
"admin_descriptionTitle": "Beschreibung der Instanz",
"admin_nameHint": "Der Name wird in der Liste öffentlicher Instanzen auf cryptpad.org angezeigt",
"admin_nameTitle": "Name der Instanz",
"support_cat_abuse": "Missbrauch melden",
"support_cat_document": "Dokument",
"support_cat_drives": "Drive oder Team",
"ui_saved": "{0} gespeichert"
}

@ -32,7 +32,7 @@
"exportPrompt": "¿Cómo te gustaría llamar a este archivo?",
"changeNamePrompt": "Cambiar tu nombre (dejar vacío para ser anónimo): ",
"clickToEdit": "Haz clic para cambiar",
"forgetPrompt": "Pulsar OK eliminará este documento del almacenamiento local (localStorage), ¿estás seguro?",
"forgetPrompt": "Pulsar OK moverá este documento a tu papelera. ¿Estás seguro?",
"shareButton": "Compartir",
"shareSuccess": "URL copiada al portapapeles",
"presentButtonTitle": "Entrar en el modo presentación",
@ -66,13 +66,13 @@
"user_displayName": "Nombre visible",
"user_accountName": "Nombre de cuenta",
"newButton": "Nuevo",
"newButtonTitle": "Nuevo documento",
"newButtonTitle": "Crear un nuevo documento",
"cancel": "Cancelar",
"poll_publish_button": "Publicar",
"poll_create_user": "Añadir usuario",
"poll_create_option": "Añadir opción",
"poll_commit": "Validar",
"fm_rootName": "Documentos",
"fm_rootName": "Drive",
"fm_trashName": "Papelera",
"fm_filesDataName": "Todos los archivos",
"fm_templateName": "Plantilla",
@ -132,7 +132,7 @@
"settings_backup": "Copia de seguridad",
"settings_restore": "Recuperar datos",
"settings_reset": "Quita todos los documentos de tu CryptDrive",
"settings_resetPrompt": "Esta acción eliminará todos tus documentos.<br>¿Seguro que quieres continuar?<br>Introduce “<em>I love CryptPad</em>” para confirmar.",
"settings_resetPrompt": "Esta acción eliminará todos los documentos de tu Drive.<br>¿Seguro que quieres continuar?<br>Introduce “<em>I love CryptPad</em>” para confirmar.",
"settings_resetDone": "¡Tu drive ahora está vacio!",
"settings_resetTips": "Consejos en CryptDrive",
"settings_resetTipsButton": "Restaurar consejos",
@ -142,7 +142,7 @@
"privacy": "Política de privacidad",
"contact": "Contacto",
"terms": "Términos de Servicio",
"movedToTrash": "Este pad fue movido a la papelera.<br><a>Acceder a mi Drive</a>",
"movedToTrash": "Este documento fue movido a la papelera.<br><a>Acceder a mi Drive</a>",
"fm_newFile": "Nuevo documento",
"fm_type": "Tipo",
"fm_categoryError": "No se pudo abrir la categoría seleccionada, mostrando la raíz.",
@ -159,7 +159,7 @@
"printOptions": "Opciones de impresión",
"printSlideNumber": "Mostrar el número de diapositiva",
"printDate": "Mostrar la fecha",
"printTitle": "Mostrar el título",
"printTitle": "Mostrar el título del documento",
"printCSS": "CSS personalizado:",
"settings_importTitle": "Importar documentos recientes locales en CryptDrive",
"settings_import": "Importar",
@ -203,8 +203,8 @@
"formattedGB": "{0} GB",
"formattedKB": "{0} KB",
"pinLimitReached": "Has llegado al límite de espacio",
"pinLimitNotPinned": "Has llegado al límite de espacio.<br>Este pad no estará presente en tu CryptDrive.",
"pinLimitDrive": "Has llegado al límite de espacio.<br>No puedes crear nuevos pads.",
"pinLimitNotPinned": "Has llegado al límite de espacio.<br>Este documento no estará presente en tu CryptDrive.",
"pinLimitDrive": "Has llegado al límite de espacio.<br>No puedes crear nuevos documentos.",
"printTransition": "Activar transiciones",
"settings_logoutEverywhereTitle": "Cerrar sesiones remotas",
"settings_logoutEverywhere": "Cerrar todas las otras sesiones",
@ -227,7 +227,7 @@
"poll_locked": "Cerrado",
"poll_unlocked": "Abierto",
"common_connectionLost": "<b>Conexión perdida</b><br>El documento está ahora en modo sólo lectura hasta que la conexión vuelva.",
"pinLimitReachedAlert": "Has llegado a tu límite de espacio. Los nuevos pads no serán guardados en tu CryptDrive.<br>Puedes eliminar pads de tu CryptDrive o <a>suscribirte a una oferta premium</a> para obtener más espacio.",
"pinLimitReachedAlert": "Has llegado a tu límite de espacio. Los nuevos documentos no serán guardados en tu CryptDrive.<br>Puedes eliminar documentos de tu CryptDrive o <a>suscribirte a una oferta premium</a> para obtener más espacio.",
"fm_info_trash": "Vacía tu papelera para liberar espacio en tu CryptDrive.",
"upload_mustLogin": "Tienes que estar conectado para subir archivos",
"uploadButton": "Subir",
@ -302,10 +302,10 @@
"main_catch_phrase": "Paquete de colaboración<br> de extremo a extremo encriptado y de código abierto",
"padNotPinned": "Este documento expirará tras 3 meses de inactividad, {0}ingresar{1} o {2}registrarse{3}para conservarlo.",
"anonymousStoreDisabled": "El administrador de esta instancia de CryptPad ha deshabilitado al almacenamiento para usuarios anónimos. Debes iniciar sesión para poder usar CryptDrive.",
"expiredError": "Este pad ha expirado y ya no está disponible.",
"expiredError": "Este documento ha expirado y ya no está disponible.",
"deletedError": "Este documento ha sido borrado y ya no se encuentra disponible.",
"inactiveError": "Esta nota ha sido eliminada por inactividad. Presione Esc para crear una nueva nota.",
"chainpadError": "Ha ocurrido un error crítico al actualizar su contenido. Esta página esta en modo de sólo lectura, para asegurarse que no perderá su trabajo.<br>Hit<em>Esc</em>para continuar y ver esta nota, o recargar para editar nuevamente.",
"inactiveError": "Este documento ha sido eliminado por inactividad. Presione Esc para crear un nuev documento.",
"chainpadError": "Ha ocurrido un error crítico al actualizar su contenido. Esta página esta en modo de sólo lectura, para asegurarse que no perderá su trabajo.<br>Hit<em>Esc</em>para continuar y ver este documento, o recargar para editar nuevamente.",
"invalidHashError": "El documento que has solicitado tiene una URL invalida.",
"errorCopy": " Aún puedes acceder esta versión en modo lectura persionando <em>Esc</em>.",
"errorRedirectToHome": "Presiona<em>Esc</em>para ser redirigido a tu Cryptdrive.",
@ -323,17 +323,17 @@
"template_import": "Importar una plantilla",
"template_empty": "No hay plantillas disponibles",
"propertiesButton": "Propiedades",
"propertiesButtonTitle": "Obtener las propiedades de esta nota",
"propertiesButtonTitle": "Obtener las propiedades de este documento",
"printButtonTitle2": "Imprimir el documento o exportar como archivo PDF",
"printBackground": "Usar una imagen de fondo",
"printBackgroundButton": "Elija una imagen",
"printBackgroundValue": "<b>Fondo de pantalla actual</b><em>{0</em>}",
"printBackgroundRemove": "Eliminar este fondo de pantalla",
"tags_title": "Etiquetas (sólo para tí)",
"tags_add": "Actualizar las etiquetas para los pads seleccionados",
"tags_add": "Actualizar las etiquetas para los documentos seleccionados",
"tags_notShared": "Tus etiquetas no están compartidas con otros usuarios",
"tags_duplicate": "Duplicar etiquetas: {0}",
"tags_noentry": "No puedes etiquetar una nota eliminada!",
"tags_noentry": "No puedes etiquetar un documento eliminado!",
"slide_invalidLess": "Estilo personalizado no válido",
"ok": "OK",
"show_help_button": "Mostrar ayuda",
@ -457,17 +457,17 @@
"settings_ownDriveTitle": "Actualizar Cuenta",
"settings_ownDriveHint": "Las cuentas más antiguas no tienen acceso a las funcionalidades más recientes. Una actualización gratuita habilitará las funcionalidades actuales y prepará su CryptDrive para las próximas actualizaciones.",
"settings_ownDriveButton": "Actualiza tu cuenta",
"padNotPinnedVariable": "Este pad caducará después de {4} días de inactividad, {0} inicie sesión {1} o {2} registre {3} para preservarlo.",
"padNotPinnedVariable": "Este documento caducará después de {4} días de inactividad, {0} inicie sesión {1} o {2} registre {3} para preservarlo.",
"register_emailWarning0": "Parece que envió su correo electrónico como su nombre de usuario.",
"register_emailWarning1": "Puedes hacerlo si usted quiere, pero no se enviara a nuestros servidores.",
"register_emailWarning2": "No podrá restablecer su contraseña utilizando su correo electrónico como puede hacerlo con muchos otros servicios.",
"register_emailWarning3": "Si de todos modos entiende y desea utilizar su correo electrónico para su nombre de usuario, haga clic en Aceptar.",
"settings_autostoreHint": "<b> Automático </b> Todos los documentos que visita se almacenan en su CryptDrive. <br> <b> Manual (preguntar siempre) </b> Si aún no ha guardado un docuemento, se le preguntará si desea para almacenarlos en su CryptDrive. <br> <b> Manual (nunca preguntar) </b> Los Documentos no se almacenan automáticamente en su CryptDrive. La opción para almacenarlos estará oculta.",
"settings_driveDuplicateTitle": "Documentos de propiedad duplicadas",
"settings_driveDuplicateHint": "Cuando mueve sus propias Pads a una carpeta compartida, se guarda una copia en su CryptDrive para asegurarse de que conserva su control sobre ella. Puedes ocultar archivos duplicados. Solo la versión compartida será visible, a menos que se elimine, en cuyo caso el original se mostrará en su ubicación anterior.",
"settings_driveDuplicateTitle": "Documentos propios duplicados",
"settings_driveDuplicateHint": "Cuando mueve sus documentos propios a una carpeta compartida, se guarda una copia en su CryptDrive para asegurarse de que conserva su control sobre ella. Puedes ocultar archivos duplicados. Solo la versión compartida será visible, a menos que se elimine, en cuyo caso el original se mostrará en su ubicación anterior.",
"settings_padWidthHint": "Cambia entre el modo de página (por defecto) que limita el ancho del editor de texto, y el uso del ancho total de la pantalla.",
"settings_padSpellcheckHint": "Esta opción le permite habilitar la corrección ortográfica en los Pads de texto enriquecido. Los errores ortográficos estarán subrayados en rojo y tendrá que mantener presionada la tecla Ctrl o Meta mientras hace clic derecho para ver las opciones correctas.",
"settings_padSpellcheckLabel": "Habilite el corrector ortográfico en el Pad de texto enriquecido",
"settings_padSpellcheckHint": "Esta opción le permite habilitar la corrección ortográfica en los documentos de texto enriquecido. Los errores ortográficos estarán subrayados en rojo y tendrá que mantener presionada la tecla Ctrl o Meta mientras hace clic derecho con el ratón para ver las opciones correctas.",
"settings_padSpellcheckLabel": "Habilite el corrector ortográfico en el documento de texto enriquecido",
"settings_ownDriveConfirm": "La actualización de su cuenta puede llevar algún tiempo. Deberá volver a iniciar sesión en todos sus dispositivos. ¿Estás seguro?",
"settings_ownDrivePending": "Su cuenta se está actualizando. No cierre ni vuelva a cargar esta página hasta que se haya completado el proceso.",
"settings_changePasswordTitle": "Cambiar tu contraseña",
@ -669,5 +669,24 @@
"footer_product": "Producto",
"admin_flushCacheDone": "Vaciado de caché exitoso",
"admin_flushCacheButton": "Vaciar caché",
"admin_flushCacheHint": "Obligar a usuarios a descargar los recursos más nuevos para el cliente (sólo si su servidor está en modo actualizado o “fresh mode”)"
"admin_flushCacheHint": "Obligar a usuarios a descargar los recursos más nuevos para el cliente (sólo si su servidor está en modo actualizado o “fresh mode”)",
"profile_friendRequestSent": "Pedido de contacto pendiente...",
"profile_info": "Otros usuarios pueden encontrar su perfil haciendo clic en su nombre en la lista de usuarios de los documentos.",
"profile_addLink": "Añade un enlace a su sitio web",
"profile_editDescription": "Edita su descripción",
"profile_addDescription": "Añadir una descripción",
"notifications_empty": "No hay notificaciones disponibles",
"friendRequest_notification": "<b>{0}</b> le envió una solicitud de contacto",
"friendRequest_received": "<b>{0}</b> quiere ser tu contato",
"friendRequest_accepted": "<b>{0}</b> aceptó su solicitud de contacto",
"friendRequest_declined": "<b>{0}</b> rechazó su solicitud de contacto",
"friendRequest_decline": "Declinar",
"friendRequest_accept": "Aceptar (Enter)",
"friendRequest_later": "Decidir después",
"drive_activeOld": "Documentos menos recientes",
"drive_active28Days": "Últimas 4 semanas",
"drive_active7Days": "Últimos 7 dias",
"drive_active1Day": "Últimas 24 horas",
"settings_codeSpellcheckLabel": "Habilite la verificación ortográfica en el editor de código",
"settings_codeSpellcheckTitle": "Corrección ortográfica"
}

@ -964,7 +964,7 @@
"todo_move": "Votre liste de tâches est désormais dans le kanban <b>{0}</b> dans votre Drive.",
"settings_safeLinkDefault": "Les liens sécurisés sont désormais activés par défaut. Veuillez utiliser le menu <i></i> <b>Partager</b> pour copier les liens plutôt que la barre d'adresse de votre navigateur.",
"support_languagesPreamble": "L'équipe de support parle les langues suivantes :",
"info_privacyFlavour": "<a>Description de la confidentialité</a> de vos données.",
"info_privacyFlavour": "<a>Déclaration de confidentialité</a> pour cette instance",
"user_about": "À propos de CryptPad",
"info_imprintFlavour": "<a>Informations légales sur les administrateurs de cette instance</a>.",
"support_cat_all": "Tout",
@ -1427,5 +1427,26 @@
"form_exportSheet": "Exporter vers Tableur",
"form_answerChoice": "Veuillez choisir comment vous souhaitez répondre à ce formulaire :",
"bounce_danger": "Le lien sur lequel vous avez cliqué ne mène pas à une page web mais à du code ou des données qui pourraient être dangereuses.\n\n(\"{0}\")\n\nCryptPad bloque ce type de lien pour des raisons de sécurité. En cliquant sur OK, vous fermerez cet onglet.",
"bounce_confirm": "Vous êtes sur le point de quitter : {0}\n\nÊtes vous sûr de vouloir visiter \"{1}\" ?"
"bounce_confirm": "Vous êtes sur le point de quitter : {0}\n\nÊtes vous sûr de vouloir visiter \"{1}\" ?",
"info_sourceFlavour": "<a>Code source</a> de CryptPad",
"info_termsFlavour": "<a>Conditions d'utilisation</a> pour cette instance",
"footer_source": "Code source",
"admin_jurisdictionHint": "Le pays où les données chiffrées de cette instance sont hébergées",
"admin_descriptionHint": "Le texte descriptif affiché pour cette instance dans la liste des instances publiques sur cryptpad.org",
"admin_descriptionTitle": "Description de l'instance",
"admin_nameHint": "Le nom affiché pour cette instance dans la liste des instances publiques sur cryptpad.org",
"admin_nameTitle": "Nom de l'instance",
"admin_archiveNote": "Note",
"support_cat_abuse": "Signaler un abus",
"support_cat_document": "Document",
"support_cat_drives": "Drive ou équipe",
"support_warning_other": "Quelle est la nature de votre demande ? Veuillez fournir autant d'informations pertinentes que possible afin de nous permettre de traiter rapidement votre problème",
"support_warning_abuse": "Merci de signaler les contenus qui ne respectent pas les <a>Conditions d'utilisation</a>. Veuillez fournir des liens vers les documents ou les profils d'utilisateurs incriminés et décrire en quoi ils enfreignent les conditions. Toute information supplémentaire sur le contexte dans lequel vous avez découvert le contenu ou le comportement peut aider les administrateurs à prévenir de futures violations",
"support_warning_bug": "Veuillez préciser dans quel navigateur le problème se produit et si des extensions sont installées. Veuillez fournir autant de détails que possible sur le problème et les étapes nécessaires pour le reproduire",
"support_warning_document": "Veuillez préciser quel type de document est à l'origine du problème et fournir la <a>référence du document</a> ou un lien",
"support_warning_drives": "Veuillez noter que les administrateurs ne sont pas en mesure d'identifier des dossiers ou documents à partir de leur nom. Pour les dossiers partagés, veuillez fournir la <a>référence du dossier</a>",
"support_warning_account": "Veuillez noter que les administrateurs ne sont pas en mesure de réinitialiser les mots de passe. Si vous avez perdu vos identifiants mais que vous êtes encore connecté, vous pouvez <a>transférer vos données vers un nouveau compte</a>",
"support_warning_prompt": "Merci de choisir la catégorie la plus pertinente pour qualifier votre problème, ceci aide les administrateurs à faire le tri et fournit des suggestions sur les informations à fournir",
"admin_jurisdictionTitle": "Pays d'hébergement",
"ui_saved": "{0} enregistré"
}

@ -1422,5 +1422,10 @@
"form_conditional_addAnd": "「かつ」の条件を追加",
"form_conditional_add": "「あるいは」の条件を追加",
"form_condition_isnot": "等しくない",
"form_condition_is": "等しい"
"form_condition_is": "等しい",
"form_template_poll": "スケジュールの投票のテンプレート",
"bounce_danger": "クリックしたリンクは、ウェブページではなく、悪質かもしれないコードやデータに結びついています。\n\n(\"{0}\")\n\nセキュリティー上の理由で、CryptPadはこのデータをブロックしています。OKをクリックするとタブが閉じます。",
"bounce_confirm": "このページから退出しようとしています: {0}\n\n次のページを開いてよろしいですか{1}",
"form_condition_hasnot": "含まない",
"form_condition_has": "含む"
}

@ -558,7 +558,7 @@
"four04_pageNotFound": "We couldn't find the page you were looking for.",
"header_logoTitle": "Go to your CryptDrive",
"header_homeTitle": "Go to CryptPad homepage",
"help_genericMore": "Learn more about how CryptPad can work for you by reading our <a>Documentation</a>.",
"help_genericMore": "Learn more about how CryptPad can work for you by reading our <a>Documentation</a>",
"edit": "edit",
"view": "view",
"feedback_about": "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions.",
@ -967,9 +967,9 @@
"slide_textCol": "Text color",
"support_languagesPreamble": "The support team speaks the following languages:",
"settings_safeLinkDefault": "Safe Links are now turned on by default. Please use the <i></i> <b>Share</b> menu to copy links rather than your browser's address bar.",
"info_imprintFlavour": "<a>Legal information about the administrators of this instance</a>.",
"info_imprintFlavour": "<a>Legal information</a> about the administrators of this instance",
"user_about": "About CryptPad",
"info_privacyFlavour": "Our <a>privacy policy</a> describes how we treat your data.",
"info_privacyFlavour": "<a>Privacy policy</a> for this instance",
"support_cat_account": "User account",
"support_cat_data": "Loss of content",
"support_cat_bug": "Bug report",
@ -1427,5 +1427,26 @@
"form_exportSheet": "Export to Sheet",
"form_answerChoice": "Please choose how you would like to answer this form:",
"bounce_confirm": "You are about to leave: {0}\n\nAre you sure you want to visit \"{1}\"?",
"bounce_danger": "The link you clicked does not lead to a web-page but to some code or data that could be malicious.\n\n(\"{0}\")\n\nCryptPad blocks these for security reasons. Clicking OK will close this tab."
"bounce_danger": "The link you clicked does not lead to a web-page but to some code or data that could be malicious.\n\n(\"{0}\")\n\nCryptPad blocks these for security reasons. Clicking OK will close this tab.",
"admin_archiveNote": "Note",
"admin_nameTitle": "Instance name",
"admin_nameHint": "The name displayed for this instance in the list of public instances on cryptpad.org",
"ui_saved": "{0} saved",
"admin_descriptionTitle": "Instance description",
"admin_descriptionHint": "The descriptive text displayed for this instance in the list of public instances on cryptpad.org",
"admin_jurisdictionTitle": "Hosting location",
"admin_jurisdictionHint": "The country where this instance's encrypted data is hosted",
"footer_source": "Source code",
"info_termsFlavour": "<a>Terms of service</a> for this instance",
"info_sourceFlavour": "<a>Source code</a> for CryptPad",
"support_warning_prompt": "Please choose the most relevant category for your issue. This helps administrators triage and provides further suggestions for what information to provide",
"support_warning_account": "Please note that administrators are not able to reset passwords. If you have lost the credentials to your account but are still logged in, you can <a>migrate your data to a new account</a>",
"support_warning_drives": "Note that administrators are not able to identify folders and documents by name. For shared folders, please provide a <a>document identifier</a>",
"support_warning_document": "Please specify which type of document is causing the issue and provide a <a>document identifier</a> or a link",
"support_warning_bug": "Please specify in which browser the issue occurs and if any extensions are installed. Please provide as much detail as possible about the issue and the steps necessary to reproduce it",
"support_warning_abuse": "Please report content that violates the <a>Terms of Service</a>. Please provide links to the offending documents or user profiles and describe how they are violating the terms. Any additional information on the context in which you discovered the content or behaviour may help administrators prevent future violations",
"support_warning_other": "What is the nature of your query? Please provide as much relevant information as possible to make it easier for us to address your issue quickly",
"support_cat_drives": "Drive or team",
"support_cat_document": "Document",
"support_cat_abuse": "Report abuse"
}

@ -13,6 +13,9 @@ define([
'/common/common-interface.js',
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO,
Cryptpad, Util, Hash, Realtime, Constants, UI) {
if (window.top !== window) {
return void window.alert(`If you are seeing this message then somebody might be trying to compromise your CryptPad account. Please contact the CryptPad development team.`);
}
window.Cryptpad = {
Common: Cryptpad,

@ -706,7 +706,7 @@ define([
}
var day = _date && allDays[_date.getDay()];
return h('div.cp-poll-cell.cp-form-poll-option', {
title: Util.fixHTML(data)
title: data,
}, [
opts.type === 'day' ? h('span.cp-form-weekday', day) : undefined,
opts.type === 'day' ? h('span.cp-form-weekday-separator', ' - ') : undefined,
@ -865,7 +865,7 @@ define([
if (totalMax.value) {
$total.find('[data-id]').removeClass('cp-poll-best');
totalMax.data.forEach(function (k) {
$total.find('[data-id="'+k+'"]').addClass('cp-poll-best');
$total.find('[data-id="'+ (k.replace(/"/g, '\\"')) + '"]').addClass('cp-poll-best');
});
}
};
@ -986,6 +986,12 @@ define([
});
});
var linkClickHandler = function (ev) {
ev.preventDefault();
var href = ($(this).attr('href') || '').trim();
if (!href) { return; }
APP.common.openUnsafeURL(href);
};
var STATIC_TYPES = {
md: {
@ -999,6 +1005,8 @@ define([
}, opts.text);
var $tag = $(tag);
DiffMd.apply(DiffMd.render(opts.text || ''), $tag, APP.common);
$tag.find('a').click(linkClickHandler);
var cursorGetter;
return {
tag: tag,
@ -2904,6 +2912,7 @@ define([
if (content.answers.msg) {
var $desc = $(description);
DiffMd.apply(DiffMd.render(content.answers.msg), $desc, APP.common);
$desc.find('a').click(linkClickHandler);
}
var actions = h('div.cp-form-submit-actions', [

@ -10,6 +10,7 @@ define([
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Cryptpad, Login, UI, Realtime, Feedback, LocalStore /*, Test */) {
if (window.top !== window) { return; }
$(function () {
var $checkImport = $('#import-recent');
if (LocalStore.isLoggedIn()) {

@ -135,6 +135,14 @@ define([
rel: 'noreferrer noopener'
}).appendTo($block).hide();
APP.$link.click(function (ev) {
ev.preventDefault();
ev.stopPropagation();
var href = $(this).attr('href').trim();
if (!href) { return; }
common.openUnsafeURL(href);
});
APP.$linkEdit = $();
if (APP.readOnly) { return; }

@ -11,9 +11,11 @@ define([
'/common/common-feedback.js',
'/common/outer/local-store.js',
'/common/hyperscript.js',
'/customize/pages.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h) {
], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages) {
if (window.top !== window) { return; }
var Messages = Cryptpad.Messages;
$(function () {
if (LocalStore.isLoggedIn()) {
@ -57,7 +59,14 @@ define([
var confirmPassword = $confirm.val();
var shouldImport = $checkImport[0].checked;
var doesAccept = $checkAcceptTerms[0].checked;
var doesAccept;
try {
// if this throws there's either a horrible bug (which someone will report)
// or the instance admins did not configure a terms page.
doesAccept = $checkAcceptTerms[0].checked;
} catch (err) {
console.error(err);
}
if (Cred.isEmail(uname) && !I_REALLY_WANT_TO_USE_MY_EMAIL_FOR_MY_USERNAME) {
var emailWarning = [
@ -93,7 +102,7 @@ define([
return void UI.alert(Messages.register_passwordsDontMatch);
}
if (!doesAccept) { // do they accept the terms of service?
if (Pages.customURLs.terms && !doesAccept) { // do they accept the terms of service? (if they exist)
return void UI.alert(Messages.register_mustAcceptTerms);
}

@ -7,6 +7,10 @@
.sidebar-layout_main();
.support_main();
.cp-dropdown-container {
margin-bottom: 0px !important;
}
.cp-hidden {
display: none !important;
}
@ -14,14 +18,14 @@
display: flex;
flex-flow: column;
.cp-support-form-attachments {
.fa {
cursor: pointer;
}
&> span {
padding: 10px;
}
}
.cp-support-form-attachments {
.fa {
cursor: pointer;
}
&> span {
padding: 10px;
}
}
.cp-support-language-list {
.cp-support-language {

@ -169,6 +169,10 @@ define([
create['subscribe'] = function () {
if (!Pages.areSubscriptionsAllowed()) { return; }
try {
if (common.getMetadataMgr().getPrivateData().plan) { return; }
} catch (err) {}
var url = Pages.accounts.upgradeURL;
var accountsLink = h('a', {
href: url,

@ -8,7 +8,8 @@ define([
'/common/clipboard.js',
'/common/common-ui-elements.js',
'/customize/messages.js',
], function ($, ApiConfig, h, UI, Hash, Util, Clipboard, UIElements, Messages) {
'/customize/pages.js',
], function ($, ApiConfig, h, UI, Hash, Util, Clipboard, UIElements, Messages, Pages) {
var getDebuggingData = function (ctx, data) {
var common = ctx.common;
@ -94,6 +95,7 @@ define([
}
};
var sendForm = function (ctx, id, form, dest) {
var $form = $(form);
var $cat = $form.find('.cp-support-form-category');
@ -103,15 +105,13 @@ define([
var $attachments = $form.find('.cp-support-attachments');
var category = $cat.val().trim();
/*
// || ($form.closest('.cp-support-list-ticket').data('cat') || "").trim();
// Messages.support_formCategoryError = "Error: category is empty"; // TODO ensure this is translated before use
/*
Messages.support_formCategoryError = "Please select a ticket category from the dropdown menu"; // TODO
if (!category) {
console.log($cat);
return void UI.alert(Messages.support_formCategoryError);
}
*/
*/
var title = $title.val().trim();
if (!title) {
@ -147,17 +147,18 @@ define([
var makeCategoryDropdown = function (ctx, container, onChange, all) {
var categories = [
// Msg.support_cat_data is left included because old tickets may still use it
'account', // Msg.support_cat_account
'data', // Msg.support_cat_data
'drives', // Msg.support_cat_drives
'document', // Msg.support_cat_document,
Pages.customURLs.terms? 'abuse': undefined, // Msg.support_cat_abuse
'bug', // Msg.support_cat_bug
// TODO report
'other' // Msg.support_cat_other
];
if (all) { categories.push('all'); } // Msg.support_cat_all
categories = categories.map(function (key) {
if (!key) { return; }
return {
tag: 'a',
content: h('span', Messages['support_cat_'+key]),
@ -178,7 +179,16 @@ define([
return $select;
};
var makeForm = function (ctx, cb, title) {
var documentIdDocs = Pages.localizeDocsLink('https://docs.cryptpad.fr/en/user_guide/apps/general.html#properties');
var warningLinks = {
account: documentIdDocs,
document: documentIdDocs,
drives: documentIdDocs,
abuse: Pages.customURLs.terms,
};
var makeForm = function (ctx, cb, title, hideNotice) {
var button;
if (typeof(cb) === "function") {
@ -193,18 +203,49 @@ define([
value: ''
});
var catContainer = h('div.cp-dropdown-container' + (title ? '.cp-hidden': ''));
var notice;
if (!(hideNotice || ctx.isAdmin)) {
notice = h('div.alert.alert-info', Messages.support_warning_prompt);
}
var clickHandler = function (ev) {
ev.preventDefault();
var $link = $(this);
var href = $link.attr('href');
if (!href) { return; }
ctx.common.openUnsafeURL(href);
};
makeCategoryDropdown(ctx, catContainer, function (key) {
$(category).val(key);
// TODO add a hint suggesting relevant information to include for the chosen category
if (!notice) { return; }
//console.log(key);
// Msg.support_warning_abuse.support_warning_account.support_warning_bug.support_warning_document.support_warning_drives.support_warning_other
var warning = Messages['support_warning_' + key] || '';
var warningLink = warningLinks[key];
if (!warningLink) {
notice.innerText = warning;
return;
}
notice.innerHTML = '';
var content = UI.setHTML(h('span'), warning);
var link = content.querySelector('a');
if (link) {
link.href = warningLink;
link.onclick = clickHandler;
}
notice.appendChild(content);
});
var attachments, addAttachment;
var content = [
h('hr'),
category,
catContainer,
h('br'),
notice,
//h('br'),
h('input.cp-support-form-title' + (title ? '.cp-hidden' : ''), {
placeholder: Messages.support_formTitle,
type: 'text',
@ -337,13 +378,14 @@ define([
$(answer).click(function () {
$ticket.find('.cp-support-form-container').remove();
$(actions).hide();
var hideNotice = true;
var form = makeForm(ctx, function () {
var sent = sendForm(ctx, content.id, form, content.sender);
if (sent) {
$(actions).css('display', '');
$(form).remove();
}
}, content.title);
}, content.title, hideNotice);
$ticket.append(form);
});
@ -471,8 +513,8 @@ define([
ui.sendForm = function (id, form, dest) {
return sendForm(ctx, id, form, dest);
};
ui.makeForm = function (cb, title) {
return makeForm(ctx, cb, title);
ui.makeForm = function (cb, title, hideNotice) {
return makeForm(ctx, cb, title, hideNotice);
};
ui.makeCategoryDropdown = function (container, onChange, all) {
return makeCategoryDropdown(ctx, container, onChange, all);

Loading…
Cancel
Save