Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

This commit is contained in:
yflory 2020-01-20 15:10:08 +01:00
commit 6d216ba1c2
10 changed files with 440 additions and 277 deletions

View File

@ -14,7 +14,7 @@
.radio-group {
display: flex;
flex-direction: row;
&:not(:last-child){
&:not(:last-child) {
margin-bottom: 8px;
}
.cp-radio {

View File

@ -284,9 +284,9 @@ module.exports.create = function (cfg) {
const storeMessage = function (ctx, channel, msg, isCp, optionalMessageHash) {
const id = channel.id;
const msgBin = Buffer.from(msg + '\n', 'utf8');
queueStorage(id, function (next) {
const msgBin = Buffer.from(msg + '\n', 'utf8');
// Store the message first, and update the index only once it's stored.
// store.messageBin can be async so updating the index first may
// result in a wrong cpIndex
@ -730,227 +730,204 @@ module.exports.create = function (cfg) {
}
};
/* onDirectMessage
* exported for use by the netflux-server
* parses and handles all direct messages directed to the history keeper
* check if it's expired and execute all the associated side-effects
* routes queries to the appropriate handlers
* GET_HISTORY
* GET_HISTORY_RANGE
* GET_FULL_HISTORY
* RPC
* if the rpc has special hooks that the history keeper needs to be aware of...
* execute them here...
const handleGetHistory = function (ctx, seq, user, parsed) {
// parsed[1] is the channel id
// parsed[2] is a validation key or an object containing metadata (optionnal)
// parsed[3] is the last known hash (optionnal)
sendMsg(ctx, user, [seq, 'ACK']);
var channelName = parsed[1];
var config = parsed[2];
var metadata = {};
var lastKnownHash;
*/
const onDirectMessage = function (ctx, seq, user, json) {
let parsed;
let channelName;
// clients can optionally pass a map of attributes
// if the channel already exists this map will be ignored
// otherwise it will be stored as the initial metadata state for the channel
if (config && typeof config === "object" && !Array.isArray(parsed[2])) {
lastKnownHash = config.lastKnownHash;
metadata = config.metadata || {};
if (metadata.expire) {
metadata.expire = +metadata.expire * 1000 + (+new Date());
}
}
metadata.channel = channelName;
metadata.created = +new Date();
Log.silly('HK_MESSAGE', json);
try {
parsed = JSON.parse(json[2]);
} catch (err) {
Log.error("HK_PARSE_CLIENT_MESSAGE", json);
return;
// if the user sends us an invalid key, we won't be able to validate their messages
// so they'll never get written to the log anyway. Let's just drop their message
// on the floor instead of doing a bunch of extra work
// TODO send them an error message so they know something is wrong
if (metadata.validateKey && !isValidValidateKeyString(metadata.validateKey)) {
return void Log.error('HK_INVALID_KEY', metadata.validateKey);
}
// If the requested history is for an expired channel, abort
// Note the if we don't have the keys for that channel in metadata_cache, we'll
// have to abort later (once we know the expiration time)
if (checkExpired(ctx, parsed[1])) { return; }
nThen(function (waitFor) {
var w = waitFor();
if (parsed[0] === 'GET_HISTORY') {
// parsed[1] is the channel id
// parsed[2] is a validation key or an object containing metadata (optionnal)
// parsed[3] is the last known hash (optionnal)
sendMsg(ctx, user, [seq, 'ACK']);
channelName = parsed[1];
var config = parsed[2];
var metadata = {};
var lastKnownHash;
// clients can optionally pass a map of attributes
// if the channel already exists this map will be ignored
// otherwise it will be stored as the initial metadata state for the channel
if (config && typeof config === "object" && !Array.isArray(parsed[2])) {
lastKnownHash = config.lastKnownHash;
metadata = config.metadata || {};
if (metadata.expire) {
metadata.expire = +metadata.expire * 1000 + (+new Date());
}
}
metadata.channel = channelName;
metadata.created = +new Date();
// if the user sends us an invalid key, we won't be able to validate their messages
// so they'll never get written to the log anyway. Let's just drop their message
// on the floor instead of doing a bunch of extra work
// TODO send them an error message so they know something is wrong
if (metadata.validateKey && !isValidValidateKeyString(metadata.validateKey)) {
return void Log.error('HK_INVALID_KEY', metadata.validateKey);
}
nThen(function (waitFor) {
var w = waitFor();
/* unless this is a young channel, we will serve all messages from an offset
this will not include the channel metadata, so we need to explicitly fetch that.
unfortunately, we can't just serve it blindly, since then young channels will
send the metadata twice, so let's do a quick check of what we're going to serve...
/* unless this is a young channel, we will serve all messages from an offset
this will not include the channel metadata, so we need to explicitly fetch that.
unfortunately, we can't just serve it blindly, since then young channels will
send the metadata twice, so let's do a quick check of what we're going to serve...
*/
getIndex(ctx, channelName, waitFor((err, index) => {
/* if there's an error here, it should be encountered
and handled by the next nThen block.
so, let's just fall through...
*/
getIndex(ctx, channelName, waitFor((err, index) => {
/* if there's an error here, it should be encountered
and handled by the next nThen block.
so, let's just fall through...
*/
if (err) { return w(); }
if (err) { return w(); }
// it's possible that the channel doesn't have metadata
// but in that case there's no point in checking if the channel expired
// or in trying to send metadata, so just skip this block
if (!index || !index.metadata) { return void w(); }
// And then check if the channel is expired. If it is, send the error and abort
// FIXME this is hard to read because 'checkExpired' has side effects
if (checkExpired(ctx, channelName)) { return void waitFor.abort(); }
// always send metadata with GET_HISTORY requests
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(index.metadata)], w);
}));
}).nThen(() => {
let msgCount = 0;
// it's possible that the channel doesn't have metadata
// but in that case there's no point in checking if the channel expired
// or in trying to send metadata, so just skip this block
if (!index || !index.metadata) { return void w(); }
// And then check if the channel is expired. If it is, send the error and abort
// FIXME this is hard to read because 'checkExpired' has side effects
if (checkExpired(ctx, channelName)) { return void waitFor.abort(); }
// always send metadata with GET_HISTORY requests
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(index.metadata)], w);
}));
}).nThen(() => {
let msgCount = 0;
// TODO compute lastKnownHash in a manner such that it will always skip past the metadata line?
getHistoryAsync(ctx, channelName, lastKnownHash, false, (msg, readMore) => {
if (!msg) { return; }
msgCount++;
// avoid sending the metadata message a second time
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)], readMore);
}, (err) => {
if (err && err.code !== 'ENOENT') {
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); }
const parsedMsg = {error:err.message, channel: channelName};
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
return;
}
// TODO compute lastKnownHash in a manner such that it will always skip past the metadata line?
getHistoryAsync(ctx, channelName, lastKnownHash, false, (msg, readMore) => {
if (!msg) { return; }
msgCount++;
// avoid sending the metadata message a second time
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)], readMore);
}, (err) => {
if (err && err.code !== 'ENOENT') {
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); }
const parsedMsg = {error:err.message, channel: channelName};
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
return;
}
const chan = ctx.channels[channelName];
const chan = ctx.channels[channelName];
if (msgCount === 0 && !metadata_cache[channelName] && chan && chan.indexOf(user) > -1) {
metadata_cache[channelName] = metadata;
if (msgCount === 0 && !metadata_cache[channelName] && chan && chan.indexOf(user) > -1) {
metadata_cache[channelName] = metadata;
// the index will have already been constructed and cached at this point
// but it will not have detected any metadata because it hasn't been written yet
// this means that the cache starts off as invalid, so we have to correct it
if (chan && chan.index) { chan.index.metadata = metadata; }
// the index will have already been constructed and cached at this point
// but it will not have detected any metadata because it hasn't been written yet
// this means that the cache starts off as invalid, so we have to correct it
if (chan && chan.index) { chan.index.metadata = metadata; }
// new channels will always have their metadata written to a dedicated metadata log
// but any lines after the first which are not amendments in a particular format will be ignored.
// Thus we should be safe from race conditions here if just write metadata to the log as below...
// TODO validate this logic
// otherwise maybe we need to check that the metadata log is empty as well
store.writeMetadata(channelName, JSON.stringify(metadata), function (err) {
if (err) {
// FIXME tell the user that there was a channel error?
return void Log.error('HK_WRITE_METADATA', {
channel: channelName,
error: err,
});
}
});
// write tasks
if(tasks && metadata.expire && typeof(metadata.expire) === 'number') {
// the fun part...
// the user has said they want this pad to expire at some point
tasks.write(metadata.expire, "EXPIRE", [ channelName ], function (err) {
if (err) {
// if there is an error, we don't want to crash the whole server...
// just log it, and if there's a problem you'll be able to fix it
// at a later date with the provided information
Log.error('HK_CREATE_EXPIRE_TASK', err);
Log.info('HK_INVALID_EXPIRE_TASK', JSON.stringify([metadata.expire, 'EXPIRE', channelName]));
}
// new channels will always have their metadata written to a dedicated metadata log
// but any lines after the first which are not amendments in a particular format will be ignored.
// Thus we should be safe from race conditions here if just write metadata to the log as below...
// TODO validate this logic
// otherwise maybe we need to check that the metadata log is empty as well
store.writeMetadata(channelName, JSON.stringify(metadata), function (err) {
if (err) {
// FIXME tell the user that there was a channel error?
return void Log.error('HK_WRITE_METADATA', {
channel: channelName,
error: err,
});
}
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(metadata)]);
}
// End of history message:
let parsedMsg = {state: 1, channel: channelName};
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
});
});
} else if (parsed[0] === 'GET_HISTORY_RANGE') {
channelName = parsed[1];
var map = parsed[2];
if (!(map && typeof(map) === 'object')) {
return void sendMsg(ctx, user, [seq, 'ERROR', 'INVALID_ARGS', HISTORY_KEEPER_ID]);
}
var oldestKnownHash = map.from;
var desiredMessages = map.count;
var desiredCheckpoint = map.cpCount;
var txid = map.txid;
if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number') {
return void sendMsg(ctx, user, [seq, 'ERROR', 'UNSPECIFIED_COUNT', HISTORY_KEEPER_ID]);
}
if (!txid) {
return void sendMsg(ctx, user, [seq, 'ERROR', 'NO_TXID', HISTORY_KEEPER_ID]);
}
sendMsg(ctx, user, [seq, 'ACK']);
return void getOlderHistory(channelName, oldestKnownHash, function (messages) {
var toSend = [];
if (typeof (desiredMessages) === "number") {
toSend = messages.slice(-desiredMessages);
} else {
let cpCount = 0;
for (var i = messages.length - 1; i >= 0; i--) {
if (/^cp\|/.test(messages[i][4]) && i !== (messages.length - 1)) {
cpCount++;
}
toSend.unshift(messages[i]);
if (cpCount >= desiredCheckpoint) { break; }
});
// write tasks
if(tasks && metadata.expire && typeof(metadata.expire) === 'number') {
// the fun part...
// the user has said they want this pad to expire at some point
tasks.write(metadata.expire, "EXPIRE", [ channelName ], function (err) {
if (err) {
// if there is an error, we don't want to crash the whole server...
// just log it, and if there's a problem you'll be able to fix it
// at a later date with the provided information
Log.error('HK_CREATE_EXPIRE_TASK', err);
Log.info('HK_INVALID_EXPIRE_TASK', JSON.stringify([metadata.expire, 'EXPIRE', channelName]));
}
});
}
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(metadata)]);
}
toSend.forEach(function (msg) {
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
JSON.stringify(['HISTORY_RANGE', txid, msg])]);
});
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
JSON.stringify(['HISTORY_RANGE_END', txid, channelName])
]);
});
} else if (parsed[0] === 'GET_FULL_HISTORY') {
// parsed[1] is the channel id
// parsed[2] is a validation key (optionnal)
// parsed[3] is the last known hash (optionnal)
sendMsg(ctx, user, [seq, 'ACK']);
// FIXME should we send metadata here too?
// none of the clientside code which uses this API needs metadata, but it won't hurt to send it (2019-08-22)
getHistoryAsync(ctx, parsed[1], -1, false, (msg, readMore) => {
if (!msg) { return; }
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(['FULL_HISTORY', msg])], readMore);
}, (err) => {
let parsedMsg = ['FULL_HISTORY_END', parsed[1]];
if (err) {
Log.error('HK_GET_FULL_HISTORY', err.stack);
parsedMsg = ['ERROR', parsed[1], err.message];
}
// End of history message:
let parsedMsg = {state: 1, channel: channelName};
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
});
} else if (rpc) {
/* RPC Calls... */
var rpc_call = parsed.slice(1);
});
};
sendMsg(ctx, user, [seq, 'ACK']);
try {
const handleGetHistoryRange = function (ctx, seq, user, parsed) {
var channelName = parsed[1];
var map = parsed[2];
if (!(map && typeof(map) === 'object')) {
return void sendMsg(ctx, user, [seq, 'ERROR', 'INVALID_ARGS', HISTORY_KEEPER_ID]);
}
var oldestKnownHash = map.from;
var desiredMessages = map.count;
var desiredCheckpoint = map.cpCount;
var txid = map.txid;
if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number') {
return void sendMsg(ctx, user, [seq, 'ERROR', 'UNSPECIFIED_COUNT', HISTORY_KEEPER_ID]);
}
if (!txid) {
return void sendMsg(ctx, user, [seq, 'ERROR', 'NO_TXID', HISTORY_KEEPER_ID]);
}
sendMsg(ctx, user, [seq, 'ACK']);
return void getOlderHistory(channelName, oldestKnownHash, function (messages) {
var toSend = [];
if (typeof (desiredMessages) === "number") {
toSend = messages.slice(-desiredMessages);
} else {
let cpCount = 0;
for (var i = messages.length - 1; i >= 0; i--) {
if (/^cp\|/.test(messages[i][4]) && i !== (messages.length - 1)) {
cpCount++;
}
toSend.unshift(messages[i]);
if (cpCount >= desiredCheckpoint) { break; }
}
}
toSend.forEach(function (msg) {
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
JSON.stringify(['HISTORY_RANGE', txid, msg])]);
});
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
JSON.stringify(['HISTORY_RANGE_END', txid, channelName])
]);
});
};
const handleGetFullHistory = function (ctx, seq, user, parsed) {
// parsed[1] is the channel id
// parsed[2] is a validation key (optionnal)
// parsed[3] is the last known hash (optionnal)
sendMsg(ctx, user, [seq, 'ACK']);
// FIXME should we send metadata here too?
// none of the clientside code which uses this API needs metadata, but it won't hurt to send it (2019-08-22)
return void getHistoryAsync(ctx, parsed[1], -1, false, (msg, readMore) => {
if (!msg) { return; }
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(['FULL_HISTORY', msg])], readMore);
}, (err) => {
let parsedMsg = ['FULL_HISTORY_END', parsed[1]];
if (err) {
Log.error('HK_GET_FULL_HISTORY', err.stack);
parsedMsg = ['ERROR', parsed[1], err.message];
}
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
});
};
const handleRPC = function (ctx, seq, user, parsed) {
if (typeof(rpc) !== 'function') { return; }
/* RPC Calls... */
var rpc_call = parsed.slice(1);
sendMsg(ctx, user, [seq, 'ACK']);
try {
// slice off the sequence number and pass in the rest of the message
rpc(ctx, rpc_call, function (err, output) {
if (err) {
@ -992,12 +969,45 @@ module.exports.create = function (cfg) {
// finally, send a response to the client that sent the RPC
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0]].concat(output))]);
});
} catch (e) {
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', 'SERVER_ERROR'])]);
}
} catch (e) {
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', 'SERVER_ERROR'])]);
}
};
/* onDirectMessage
* exported for use by the netflux-server
* parses and handles all direct messages directed to the history keeper
* check if it's expired and execute all the associated side-effects
* routes queries to the appropriate handlers
*/
const onDirectMessage = function (ctx, seq, user, json) {
Log.silly('HK_MESSAGE', json);
let parsed;
try {
parsed = JSON.parse(json[2]);
} catch (err) {
Log.error("HK_PARSE_CLIENT_MESSAGE", json);
return;
}
// If the requested history is for an expired channel, abort
// Note the if we don't have the keys for that channel in metadata_cache, we'll
// have to abort later (once we know the expiration time)
if (checkExpired(ctx, parsed[1])) { return; }
if (parsed[0] === 'GET_HISTORY') {
return void handleGetHistory(ctx, seq, user, parsed);
}
if (parsed[0] === 'GET_HISTORY_RANGE') {
return void handleGetHistoryRange(ctx, seq, user, parsed);
}
if (parsed[0] === 'GET_FULL_HISTORY') {
return void handleGetFullHistory(ctx, seq, user, parsed);
}
return void handleRPC(ctx, seq, user, parsed);
};
return {
id: HISTORY_KEEPER_ID,
setConfig: setConfig,

View File

@ -1099,7 +1099,7 @@ define([
if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }
// check if the pad is password protected
// check if the pad is password protected
var hash = hashes.editHash || hashes.viewHash;
var href = origin + pathname + '#' + hash;
var parsedHref = Hash.parsePadUrl(href);
@ -1107,7 +1107,7 @@ define([
var makeFaqLink = function () {
var link = h('span', [
h('i.fa.fa-question-circle'),
h('i.fa.fa-question-circle'),
h('a', {href: '#'}, Messages.passwordFaqLink)
]);
$(link).click(function () {
@ -1115,7 +1115,7 @@ define([
});
return link;
};
var parsed = Hash.parsePadUrl(pathname);
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
@ -1130,8 +1130,8 @@ define([
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
UI.createRadio('accessRights', 'cp-share-editable-true',
Messages.share_linkEdit, false, { mark: {tabindex:1} })]),
burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading ||
'View once and self-destruct', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX temp KEY
burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading,
false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined
]);
// Burn after reading
@ -1140,7 +1140,7 @@ define([
// the options to generate the BAR url
var barAlert = h('div.alert.alert-danger.cp-alertify-bar-selected', {
style: 'display: none;'
}, Messages.burnAfterReading_warningLink || " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server."); // XXX temp KEY
}, Messages.burnAfterReading_warningLink);
var channel = Hash.getSecrets('pad', hash, config.password).channel;
common.getPadMetadata({
channel: channel
@ -1182,7 +1182,7 @@ define([
cb(url);
});
}
return Messages.burnAfterReading_generateLink || 'Click on the button below to generate a link'; // XXX temp KEY
return Messages.burnAfterReading_generateLink;
}
var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash;
var href = burnAfterReading ? burnAfterReadingUrl : (origin + pathname + '#' + hash);
@ -1212,18 +1212,18 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
linkContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'),
h('i.fa.fa-lock'),
Messages.share_linkPasswordAlert, h('br'),
makeFaqLink()
]));
}
// warning about sharing links
// warning about sharing links
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
h('span.cp-inline-alert-text', Messages.share_linkWarning),
dismissButton
]);
@ -1305,9 +1305,9 @@ define([
var contactsContent = h('div.cp-share-modal');
var $contactsContent = $(contactsContent);
$contactsContent.append(friendsList);
// Show alert if the pad is password protected
if (hasPassword) {
$contactsContent.append(h('div.alert.alert-primary', [
@ -1348,7 +1348,7 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
embedContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'), ' ',
h('i.fa.fa-lock'), ' ',
Messages.share_embedPasswordAlert, h('br'),
makeFaqLink()
]));
@ -1494,13 +1494,13 @@ define([
if (!hashes.fileHash) { throw new Error("You must provide a file hash"); }
var url = origin + pathname + '#' + hashes.fileHash;
// check if the file is password protected
// check if the file is password protected
var parsedHref = Hash.parsePadUrl(url);
var hasPassword = parsedHref.hashData.password;
var makeFaqLink = function () {
var link = h('span', [
h('i.fa.fa-question-circle'),
h('i.fa.fa-question-circle'),
h('a', {href: '#'}, Messages.passwordFaqLink)
]);
$(link).click(function () {
@ -1532,12 +1532,12 @@ define([
]));
}
// warning about sharing links
// warning about sharing links
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
h('span.cp-inline-alert-text', Messages.share_linkWarning),
dismissButton
]);
@ -1589,7 +1589,7 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
$contactsContent.append(h('div.alert.alert-primary', [
h('i.fa.fa-unlock'),
h('i.fa.fa-unlock'),
Messages.share_contactPasswordAlert, h('br'),
makeFaqLink()
]));
@ -1615,7 +1615,7 @@ define([
// Show alert if the pad is password protected
if (hasPassword) {
embed.append(h('div.alert.alert-primary', [
h('i.fa.fa-lock'), ' ',
h('i.fa.fa-lock'), ' ',
Messages.share_embedPasswordAlert, h('br'),
makeFaqLink()
]));
@ -1862,7 +1862,7 @@ define([
seeds: seeds,
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
waitFor.abort();
$(linkSpin).hide();
$(linkForm).show();
$nav.find('button.cp-teams-invite-create').show();
@ -4055,8 +4055,8 @@ define([
};
UIElements.displayBurnAfterReadingPage = function (common, cb) {
var info = h('p.cp-password-info', Messages.burnAfterReading_warning || 'This document will self-destruct as soon as you open it. It will be removed form the server, once you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later. '); // XXX temp KEY
var button = h('button.primary', Messages.burnAfterReading_proceed || 'view and delete'); // XXX temp KEY
var info = h('p.cp-password-info', Messages.burnAfterReading_warning);
var button = h('button.primary', Messages.burnAfterReading_proceed);
$(button).on('click', function () {
cb();
@ -4071,7 +4071,7 @@ define([
UIElements.getBurnAfterReadingWarning = function (common) {
var priv = common.getMetadataMgr().getPrivateData();
if (!priv.burnAfterReading) { return; }
return h('div.alert.alert-danger.cp-burn-after-reading', Messages.burnAfterReading_warningDeleted || 'This pad has been deleted from the server, once you close this window you will not be able to access it again.'); // XXX temp KEY
return h('div.alert.alert-danger.cp-burn-after-reading', Messages.burnAfterReading_warningDeleted);
};
var crowdfundingState = false;

View File

@ -376,7 +376,7 @@
"fm_tags_used": "Nombre d'usos",
"fm_restoreDrive": "Reconfigura la vostra unitat a un estat anterior. Per uns bons resultats, eviteu fer canvis a la unitat fins que el procés s'hagi completat.",
"fm_moveNestedSF": "No podeu col·locar una carpeta compartida dins d'una altra. La carpeta {0} no s'ha desplaçat.",
"fm_passwordProtected": "Aquest document està protegit amb contrasenya",
"fm_passwordProtected": "Protegit amb contrasenya",
"fc_newfolder": "Carpeta nova",
"fc_newsharedfolder": "Carpeta compartida nova",
"fc_rename": "Reanomena",
@ -506,5 +506,30 @@
"settings_deleteButton": "Suprimir el vostre compte",
"settings_deleteModal": "Envieu la següent informació a qui administri el vostre CryptPad per tal que les vostres dades siguin esborrades del servidor.",
"settings_deleteConfirm": "Segur que voleu suprimir el vostre compte? Aquesta acció és irreversible.",
"settings_deleted": "El vostre compte ha estat suprimit. Premeu D'acord per tornar a la pàgina inicial."
"settings_deleted": "El vostre compte ha estat suprimit. Premeu D'acord per tornar a la pàgina inicial.",
"storageStatus": "Emmagatzematge:<br /><b>{0}</b> utilitzats de <b>{1}</b>",
"settings_anonymous": "No heu iniciat la sessió. Els paràmetres només s'aplicaran al navegador actual.",
"settings_publicSigningKey": "Clau pública de signatura",
"settings_usage": "Utilització",
"settings_usageTitle": "Mostra la mida total dels vostres documents fixats en MB",
"settings_pinningNotAvailable": "Els documents fixats només estan disponibles si us heu registrat.",
"settings_pinningError": "Alguna cosa no ha funcionat correctament",
"settings_usageAmount": "Els vostres documents fixats ocupen {0} MB",
"settings_logoutEverywhereButton": "Tanca la sessió",
"settings_logoutEverywhereTitle": "Tanca la sessió arreu",
"settings_logoutEverywhere": "Tanca totes les altres sessions",
"settings_logoutEverywhereConfirm": "De debò? Haureu de tornar a iniciar la vostra sessió a tots els dispositius.",
"settings_driveDuplicateTitle": "Documents propis duplicats",
"settings_driveDuplicateHint": "Quan moveu els vostres documents a una carpeta compartida, es manté una còpia al vostre CryptDrive per assegurar que en continueu tenint el control. Podeu amagar els fitxers duplicats. Només serà visible la versió compartida, excepte si l'elimineu, en aquest cas es mostrarà l'original en la seva ubicació original.",
"settings_driveDuplicateLabel": "Amaga els duplicats",
"settings_codeIndentation": "Esquerdes a l'editor de codi (espais)",
"settings_codeUseTabs": "Espaiar utilitzant tabulacions (enlloc d'espais)",
"settings_codeFontSize": "Mida de la lletra a l'editor de codi",
"settings_padWidth": "Amplada màxima de l'editor",
"settings_padWidthHint": "Els documents de text enriquit utilitzen, per defecte, l'amplada màxima de la vostra pantalla, fet que pot dificultar la lectura. Aquí podeu reduir l'amplada de l'editor.",
"settings_padWidthLabel": "Redueix l'amplada de l'editor",
"settings_padSpellcheckTitle": "Correcció ortogràfica",
"settings_padSpellcheckHint": "Aquesta opció us permet habilitar la correcció ortogràfica als documents de text. Les errades es subratllaran en vermell i haureu de mantenir apretada la tecla Ctrl o Meta mentre cliqueu el botó dret per veure les opcions correctes.",
"settings_padSpellcheckLabel": "Activa la correcció ortogràfica",
"settings_creationSkip": "Salta la pantalla de creació de document"
}

View File

@ -1200,7 +1200,7 @@
"team_avatarTitle": "Teamavatar",
"team_avatarHint": "Maximale Größe ist 500 KB (png, jpg, jpeg, gif)",
"team_infoContent": "Jedes Team hat eigene CryptDrives, Speicherplatzbegrenzungen, Chats und Mitgliederlisten. Eigentümer können das gesamte Team löschen. Admins können Mitglieder einladen oder entfernen. Mitglieder können das Team verlassen.",
"team_maxOwner": "Jeder Benutzer kann nur Eigentümer eines Teams sein.",
"team_maxOwner": "Jeder Benutzer kann nur Eigentümer eines einzigen Teams sein.",
"team_maxTeams": "Jeder Benutzer kann nur Mitglied von {0} Teams sein.",
"team_listTitle": "Deine Teams",
"team_listSlot": "Verfügbare Teamplätze",

View File

@ -12,7 +12,8 @@
"todo": "Lista de tareas",
"file": "Archivo",
"media": "Media",
"sheet": "Hoja (Beta)"
"sheet": "Hoja (Beta)",
"teams": "Equipos"
},
"disconnected": "Desconectado",
"synchronizing": "Sincronizando",
@ -260,7 +261,12 @@
"indent": "Cuando editas listas, puedes usar tab o shift+tab para incrementar o decrementar la sangría.",
"store": "Cada vez que visitas un pad con una sesión iniciada se guardará en tu CryptDrive.",
"marker": "Puedes resaltar texto en un pad utilizando el \"marcador\" en el menú de estílo.",
"driveUpload": "Los usuarios registrados pueden subir archivos cifrados arrastrándolos hacia CryptDrive."
"driveUpload": "Los usuarios registrados pueden subir archivos cifrados arrastrándolos hacia CryptDrive.",
"filenames": "Puede renombrar los archivos de su CryptDrive, este nombre es sólo para usted.",
"drive": "Los usuarios conectados pueden organizar sus archivos en su CryptDrive, a los que se puede acceder desde el icono del CryptPad situado en la parte superior izquierda de todos los pads.",
"profile": "Los usuarios registrados pueden crear un perfil desde el menú de usuario en la parte superior derecha.",
"avatars": "Puedes subir un avatar en tu perfil. La gente lo verá cuando colabores en un pad.",
"tags": "Etiquete sus pads e inicie una búsqueda con # en su CryptDrive para encontrarlos"
},
"feedback_about": "Si estas leyendo esto, quizás sientas curiosidad por saber por qué CryptPad solicita páginas cuando realizas algunas acciones",
"feedback_privacy": "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos este archivo para conocer las funcionalidades que importan a nuestros usuarios, pidiéndolo con un parametro que nos dice qué acción fue realizada.",
@ -814,6 +820,10 @@
"crypto": {
"q": "¿Qué criptografía usas?",
"a": "CryptPad se basa en dos bibliotecas de criptografía de código abierto: <a href='https://github.com/dchest/tweetnacl-js' target='_blank'> tweetnacl.js </a> y <a href = 'https : //github.com/dchest/scrypt-async-js 'target =' _ blank '> scrypt-async.js </a>. <br> <br> Scrypt es un <em> algoritmo de derivación de clave basado en contraseña < / em>. Lo usamos para convertir su nombre de usuario y contraseña en un llavero único que asegura el acceso a su CryptDrive de modo que solo usted pueda acceder a su lista de almohadillas. <br> <br> Utilizamos <em> xsalsa20-poly1305 </em> y <em> x25519-xsalsa20-poly1305 </em> cifrados proporcionados por tweetnacl para cifrar los pads y el historial de chat, respectivamente."
},
"pad_password": {
"q": "¿Qué sucede cuando protejo un pad/carpeta con una contraseña?",
"a": "Puede proteger cualquier bloc o carpeta compartida con una contraseña al crearla. También puede utilizar el menú de propiedades para establecer/cambiar/eliminar una contraseña en cualquier momento.<br><br>Las contraseñas del pad y de la carpeta compartida están pensadas para proteger el enlace cuando lo comparta a través de canales potencialmente inseguros como el correo electrónico o los mensajes de texto. Si alguien intercepta su enlace pero no tiene la contraseña, no podrá leer su documento.<br><br>Al compartir dentro de CryptPad con sus contactos o equipos, las comunicaciones se cifran y suponemos que desea que accedan a su documento. Por lo tanto, la contraseña se recuerda y se envía con el pad cuando lo comparte. Al destinatario, o a usted mismo, se le pide <b>not</b> cuando abre el documento."
}
},
"usability": {
@ -903,7 +913,8 @@
"slide": {
"markdown": "Escriba diapositivas en <a href=\"http://www.markdowntutorial.com/\"> Reducción </a> y sepárelas con una línea que contenga <code> --- </code>",
"present": "Comenzar la presentación usando el <span class=\"fa fa-play-circle\"></span> botón",
"colors": "Cambia el texto y el color de fondo usando el <span class=\"fa fa-i-cursor\"></span> y <span class=\"fa fa-square\"></span> botones"
"colors": "Cambia el texto y el color de fondo usando el <span class=\"fa fa-i-cursor\"></span> y <span class=\"fa fa-square\"></span> botones",
"settings": "Cambiar los ajustes de las diapositivas (fondo, transiciones, números de página, etc.) con el botón <span class=\"fa fa fa-cog\"></span> en el submenú <span class=\"fa fa-ellipsis-h\"></span>"
},
"poll": {
"decisions": "Crea decisiones en privado entre verdaderos amigos",
@ -913,7 +924,20 @@
},
"whiteboard": {
"colors": "Haga doble clic en los colores para modificar su paleta",
"mode": "Deshabilite el modo de dibujo para arrastrar y estirar trazos"
"mode": "Deshabilite el modo de dibujo para arrastrar y estirar trazos",
"embed": "Incrusta imágenes de tu disco <span class=\"fa fa-file-image-o\"></span> o de tu CryptDrive <span class=\"fa fa-image\"></span> y expórtalas como PNG a tu disco <span class=\"fa fa-download\"></span> o a tu CryptDrive <span class=\"fa fa-cloud-upload\"></span>"
},
"kanban": {
"add": "Añada nuevas placas usando el botón <span class=\"fa fa-plus\"></span> en la esquina superior derecha",
"task": "Mover objetos arrastrándolos y soltándolos de un tablero a otro",
"color": "Cambie los colores haciendo clic en la parte coloreada junto a los títulos de los tableros"
}
}
},
"storageStatus": "Almacenamiento:<br /><b>{0}</b> utilizado de <b>{1}</b>",
"settings_cursorColorTitle": "Color del cursor",
"mdToolbar_button": "Mostrar u ocultar la barra de herramientas de rebajas",
"creation_404": "Esta almohadilla ya no existe. Utilice el siguiente formulario para crear un nuevo pad.",
"creation_ownedTitle": "Tipo de pad",
"creation_owned1": "Un pad <b>propio</b> puede ser eliminado del servidor cuando el propietario lo desee. Al eliminar un pad propio se elimina de los CryptDrives de otros usuarios.",
"creation_owned2": "Un pad <b>open</b> no tiene dueño y por lo tanto, no puede ser eliminado del servidor a menos que haya llegado a su tiempo de expiración."
}

View File

@ -749,5 +749,66 @@
"team_inviteGetData": "Haetaan tiimitietoja",
"team_cat_link": "Kutsulinkki",
"team_links": "Kutsulinkit",
"team_inviteInvalidLinkError": "Tämä kutsulinkki ei ole kelvollinen."
"team_inviteInvalidLinkError": "Tämä kutsulinkki ei ole kelvollinen.",
"notificationsPage": "",
"openNotificationsApp": "",
"notifications_cat_all": "",
"owner_request_declined": "",
"owner_removed": "",
"owner_removedPending": "",
"team_pcsSelectLabel": "",
"team_pcsSelectHelp": "",
"team_invitedToTeam": "",
"team_kickedFromTeam": "",
"team_acceptInvitation": "",
"team_declineInvitation": "",
"team_cat_general": "",
"team_cat_list": "",
"team_cat_create": "",
"team_cat_back": "",
"team_cat_members": "",
"team_cat_chat": "",
"team_cat_drive": "",
"team_cat_admin": "",
"team_infoLabel": "",
"team_listLoad": "",
"team_createLabel": "",
"team_createName": "",
"team_rosterPromote": "",
"team_rosterDemote": "",
"team_rosterKick": "",
"team_inviteButton": "",
"team_leaveButton": "",
"team_leaveConfirm": "",
"team_owner": "",
"team_admins": "",
"team_members": "",
"team_nameTitle": "",
"team_nameHint": "",
"team_avatarTitle": "",
"team_avatarHint": "",
"team_infoContent": "",
"team_maxOwner": "",
"team_maxTeams": "",
"team_listTitle": "",
"team_listSlot": "",
"owner_addTeamText": "",
"owner_team_add": "",
"team_rosterPromoteOwner": "",
"team_ownerConfirm": "",
"team_kickConfirm": "",
"sent": "",
"team_pending": "",
"team_deleteTitle": "",
"team_deleteHint": "",
"team_deleteButton": "",
"team_deleteConfirm": "",
"team_pendingOwner": "",
"team_pendingOwnerTitle": "",
"team_demoteMeConfirm": "",
"team_title": "",
"team_quota": "",
"drive_quota": "",
"settings_codeBrackets": "",
"team_viewers": ""
}

View File

@ -33,7 +33,7 @@
"inactiveError": "Ce pad a été supprimé en raison de son inactivité. Appuyez sur Échap pour créer un nouveau pad.",
"chainpadError": "Une erreur critique est survenue lors de la mise à jour du contenu. Le pad est désormais en mode lecture seule afin de s'assurer que vous ne perdiez pas davantage de données.<br>Appuyez sur <em>Échap</em> pour voir le pad ou rechargez la page pour pouvoir le modifier à nouveau.",
"invalidHashError": "L'URL du document demandé n'est pas valide.",
"errorCopy": " Vous pouvez toujours copier son contenu ailleurs en appuyant sur <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer !",
"errorCopy": " Vous pouvez accéder au contenu en appuyant sur <em>Échap</em>.<br>Quand vous fermerez cette page, il sera définitivement supprimé.",
"errorRedirectToHome": "Appuyez sur <em>Échap</em> pour retourner vers votre CryptDrive.",
"newVersionError": "Une nouvelle version de CryptPad est disponible.<br><a href='#'>Rechargez la page</a> pour utiliser la nouvelle version, ou appuyez sur Échap pour accéder au contenu actuel en <b>mode hors-ligne</b>.",
"loading": "Chargement...",
@ -971,7 +971,7 @@
"share_linkView": "Lecture-seule",
"share_linkEmbed": "Mode intégration (cache la barre d'outils)",
"share_linkPresent": "Présenter",
"share_linkOpen": "Apperçu",
"share_linkOpen": "Aperçu",
"share_linkCopy": "Copier",
"share_embedCategory": "Intégration",
"share_mediatagCopy": "Copier le mediatag",
@ -1281,5 +1281,11 @@
"team_cat_link": "Invitation",
"team_links": "Liens d'invitation",
"team_inviteInvalidLinkError": "Ce lien d'invitation n'est pas valide.",
"team_inviteLinkError": "Erreur lors de la génération du lien."
"team_inviteLinkError": "Erreur lors de la génération du lien.",
"burnAfterReading_linkBurnAfterReading": "Voir et autodétruire",
"burnAfterReading_warningLink": "Vous avez choisi d'autodétruire ce pad. Quand votre destinataire ouvrira ce lien, le pad sera visible une fois avant d'être définitivement supprimé.",
"burnAfterReading_generateLink": "Cliquer sur le bouton ci-dessous pour générer un lien.",
"burnAfterReading_warningAccess": "Ce document s'autodétruira dès son ouverture. Lorsque vous cliquez sur le bouton ci-dessous, vous verrez le contenu une fois avant qu'il ne soit définitivement supprimé. Lorsque vous fermerez cette fenêtre, vous ne pourrez plus y accéder. Si vous n'êtes pas prêt à continuer, vous pouvez fermer cette fenêtre et revenir plus tard.",
"burnAfterReading_proceed": "voir et supprimer",
"burnAfterReading_warningDeleted": "Ce pad a été définitivement supprimé, si vous fermez cette fenêtre vous ne pourrez plus y accéder."
}

View File

@ -12,7 +12,8 @@
"media": "Media",
"todo": "Todo",
"contacts": "Contatti",
"sheet": "Fogli (Beta)"
"sheet": "Fogli (Beta)",
"teams": "Team"
},
"button_newpad": "Nuovo pad di Testo",
"button_newcode": "Nuovo pad di Code",
@ -40,7 +41,7 @@
"error": "Errore",
"saved": "Salvato",
"synced": "È stato tutto salvato",
"deleted": "Pad cancellato dal tuo CryptDrive",
"deleted": "Cancellato",
"deletedFromServer": "Pad cancellato dal server",
"mustLogin": "Devi essere loggato per poter accedere a questa pagina",
"disabledApp": "Questa applicazione è stata disattivata. Contatta l'amministratore di questo CryptPad per ulteriori informazioni.",
@ -52,7 +53,7 @@
"initializing": "Inizializzazione...",
"forgotten": "Spostato nel cestino",
"errorState": "Errore critico: {0}",
"lag": "Lag",
"lag": "Latenza",
"readonly": "Solo lettura",
"anonymous": "Anonimo",
"yourself": "Da solo",
@ -66,7 +67,7 @@
"editors": "editori",
"userlist_offline": "Sei offline, la lista degli utenti non è disponibile.",
"language": "Lingua",
"comingSoon": "Coming soon...",
"comingSoon": "In arrivo...",
"newVersion": "<b>CryptPad è stato aggiornato!</b><br>Scopri cosa c'è di nuovo nell'ultima versione:<br><a href=\"https://github.com/xwiki-labs/cryptpad/releases/tag/{0}\" target=\"_blank\">Note di rilascio per CryptPad {0}</a>",
"upgrade": "Aumenta il limite",
"upgradeTitle": "Effettua l'upgrade del tuo account per incrementare il limite di spazio",
@ -82,10 +83,10 @@
"orangeLight": "La tua connessione è lenta, questo potrebbe peggiorare la tua esperienza",
"redLight": "Sei stato disconnesso dalla sessione",
"pinLimitReached": "Hai raggiunto il limite di spazio",
"pinLimitReachedAlert": "Hai raggiunto il limite di spazio. I nuovi pad non saranno salvati nel tuo CryptDrive.<br>You can either remove pads from your CryptDrive or <a href=\"https://accounts.cryptpad.fr/#!on={0}\" target=\"_blank\">effettua l'upgrade</a> per incrementare il tuo limite.",
"pinLimitReachedAlert": "Hai raggiunto il limite di spazio. I nuovi pad non saranno salvati nel tuo CryptDrive.<br>Puoi rimuovere dei pad dal tuo CryptDrive o <a href=\"https://accounts.cryptpad.fr/#!on={0}\" target=\"_blank\">effettuare l'upgrade del tuo account</a> per incrementare il tuo limite.",
"pinLimitReachedAlertNoAccounts": "Hai raggiunto il limite di spazio",
"pinLimitNotPinned": "Hai raggiunto il limite di spazio. <br>Questo pad non è salvato nel tuo CryptDrive.",
"pinLimitDrive": "Hai raggiunto il limite di spazio.<br>Non puoi creare nuovi pads.",
"pinLimitDrive": "Hai raggiunto il limite di spazio.<br>Non puoi creare nuovi pad.",
"moreActions": "Altre azioni",
"importButton": "Importa",
"importButtonTitle": "Importa un pad da un file locale",
@ -109,7 +110,7 @@
"userAccountButton": "Il tuo account",
"newButton": "Nuovo",
"newButtonTitle": "Crea un nuovo pad",
"uploadButton": "Carica files",
"uploadButton": "Carica file",
"uploadButtonTitle": "Carica un nuovo file nella cartella corrente",
"saveTemplateButton": "Salva come modello",
"saveTemplatePrompt": "Scegli un titolo per il modello",
@ -143,7 +144,7 @@
"filePickerButton": "Inserisci un file salvato in CryptDrive",
"filePicker_close": "Chiudi",
"filePicker_description": "Scegli un file dal tuo CryptDrive da inserire oppure caricane uno nuovo",
"filePicker_filter": "Filtra files per nome",
"filePicker_filter": "Filtra file per nome",
"or": "o",
"tags_title": "Tags (mostrati solo a te)",
"tags_add": "Aggiorna i tags di questa pagina",
@ -307,12 +308,12 @@
"debug_getGraphText": "Questo è il codice DOT per generare un grafo della storia di questo documento:",
"fm_rootName": "Documenti",
"fm_trashName": "Cestino",
"fm_unsortedName": "Files non catalogati",
"fm_filesDataName": "Tutti i files",
"fm_unsortedName": "File non catalogati",
"fm_filesDataName": "Tutti i file",
"fm_templateName": "Modelli",
"fm_searchName": "Cerca",
"fm_recentPadsName": "Pads recenti",
"fm_ownedPadsName": "Pads posseduti",
"fm_recentPadsName": "Pad recenti",
"fm_ownedPadsName": "Pad posseduti",
"fm_tagsName": "Tags",
"fm_sharedFolderName": "Cartella condivisa",
"fm_searchPlaceholder": "Cerca...",
@ -324,7 +325,7 @@
"fm_sharedFolder": "Cartella condivisa",
"fm_folderName": "Nome della cartella",
"fm_numberOfFolders": "# delle cartelle",
"fm_numberOfFiles": "# dei files",
"fm_numberOfFiles": "# dei file",
"fm_fileName": "Nome del file",
"fm_title": "Titolo",
"fm_type": "Tipo",
@ -336,27 +337,27 @@
"fm_noname": "Documento senza titolo",
"fm_emptyTrashDialog": "Sei sicuro di voler svuotare il cestino?",
"fm_removeSeveralPermanentlyDialog": "Sei sicuro di voler rimuovere permanentemente questi {0} elementi dal tuo CryptDrive?",
"fm_removePermanentlyNote": "I pads posseduti saranno rimossi dal server, continuando.",
"fm_removePermanentlyNote": "I pad posseduti verranno rimossi dal server, se continui.",
"fm_removePermanentlyDialog": "Sei sicuro di voler rimuovere permanentemente questo elemento dal tuo CryptDrive?",
"fm_removeSeveralDialog": "Sei sicuro di voler spostare questi {0} elementi nel cestino?",
"fm_removeDialog": "Sei sicuro di voler spostare {0} nel cestino?",
"fm_deleteOwnedPad": "Sei sicuro di voler rimuovere permanentemente questo pad dal server?",
"fm_deleteOwnedPads": "Sei sicuro di voler rimuovere permanentemente questi pads dal server?",
"fm_deleteOwnedPads": "Sei sicuro di voler rimuovere permanentemente questi pad dal server?",
"fm_restoreDialog": "Sei sicuro di voler ripristinare {0} nella sua precedente posizione?",
"fm_unknownFolderError": "La directory selezionata o visitata precedentemente non esiste più. Sto aprendo la cartella superiore...",
"fm_contextMenuError": "Impossibile aprire il menu contestuale per questo elemento. Se il problema persiste, prova a ricaricare la pagina.",
"fm_selectError": "Impossibile selezionare l'elemento desiderato. Se il problema persiste, prova a ricaricare la pagina.",
"fm_categoryError": "Impossibile aprire la categoria selezionata, mostro Documenti.",
"fm_info_root": "Crea quante cartelle desideri per contenere tutti i tuoi files.",
"fm_info_unsorted": "Contiene tutto i files che hai visitato che non sono stati ancora salvati in \"Documenti\" o spostati nel \"Cestino\".",
"fm_info_template": "Contiene tutti i pads salvati come modelli e che puoi riutilizzare per creare nuovi pad.",
"fm_info_recent": "Lista dei pads recentemente aperti o modificati.",
"fm_info_root": "Crea quante cartelle desideri per contenere tutti i tuoi file.",
"fm_info_unsorted": "Contiene tutto i file che hai visitato che non sono stati ancora salvati in \"Documenti\" o spostati nel \"Cestino\".",
"fm_info_template": "Contiene tutti i pad salvati come modelli e che puoi riutilizzare per creare nuovi pad.",
"fm_info_recent": "Lista dei pad recentemente aperti o modificati.",
"fm_info_trash": "Svuota il tuo cestino per liberare spazio nel tuo CryptDrive.",
"fm_info_allFiles": "Contiene tutti i files da \"Documenti\", \"Non catalogati\", \"Cestino\". Non puoi muovere o cancellare files da qui.",
"fm_info_anonymous": "Non sei loggato, quindi i tuoi pads scadranno tra tre mesi (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">find out more</a>). Sono salvati nel tuo browser, quindi cancellando la cronologia potresti farli scomparire.<br><a href=\"/register/\">Registrati</a> o <a href=\"/login/\">Effettua il login</a> per salvarti permanentemente.<br>",
"fm_info_allFiles": "Contiene tutti i file da \"Documenti\", \"Non catalogati\", \"Cestino\". Non puoi muovere o cancellare file da qui.",
"fm_info_anonymous": "Non hai effettuato l'accesso, quindi i tuoi pad scadranno fra tre mesi (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">scopri di più</a>). Sono conservati nel tuo browser, quindi cancellando la cronologia potresti farli scomparire.<br><a href=\"/register/\">Registrati</a> o <a href=\"/login/\">Accedi</a> per conservarli permanentemente.<br>",
"fm_info_sharedFolder": "Questa è una cartella condivisa. Non sei loggato, quindi puoi accederla solo in modalità solo lettura.<br><a href=\"/register/\">Registrati</a> o <a href=\"/login/\">Effettua il login</a> per poterla importare nel tuo CryptDrive e per modificarla.",
"fm_info_owned": "Sei il proprietario dei pads mostrati qui. Questo significa che puoi rimuoverli permanentemente dal server, quando lo desideri. Se lo fai, gli altri utenti non potranno mai più accedervi.",
"fm_alert_backupUrl": "Link di backup per questo drive.<br>È<strong>estremamente raccomandato</strong> che tu lo tenga segreto.<br>Puoi usarlo per recuperare tutti i tuoi files nel caso in cui la memoria del tuo browser venga cancellata.<br>Chiunque in possesso di questo link può modificare o rimuovere tutti i files nel tuo file manager.<br>",
"fm_info_owned": "Sei il proprietario dei pad mostrati qui. Questo significa che puoi rimuoverli permanentemente dal server, quando lo desideri. Se lo fai, gli altri utenti non potranno più accedervi.",
"fm_alert_backupUrl": "Link di backup per questo drive.<br>È<strong>estremamente raccomandato</strong> che tu lo tenga segreto.<br>Puoi usarlo per recuperare tutti i tuoi file nel caso in cui la memoria del tuo browser venga cancellata.<br>Chiunque in possesso di questo link può modificare o rimuovere tutti i file nel tuo file manager.<br>",
"fm_backup_title": "Link di backup",
"fm_nameFile": "Come vuoi chiamare questo file?",
"fm_error_cantPin": "Errore interno del server. Per favore, ricarica la pagina e prova di nuovo.",
@ -374,7 +375,7 @@
"fm_tags_used": "Numero di utilizzi",
"fm_restoreDrive": "Ripristina il tuo drive ad uno stato precedente. Per i migliori risultati, evita di fare cambiamenti al tuo drive sinchè questo processo non sarà completato.",
"fm_moveNestedSF": "Non puoi spostare una cartella condivisa all'interno di un'altra. La cartella {0} non è stata spostata.",
"fm_passwordProtected": "Questo documento è protetto da una password",
"fm_passwordProtected": "Protetto da password",
"fc_newfolder": "Nuova cartella",
"fc_newsharedfolder": "Nuova cartella condivisa",
"fc_rename": "Rinomina",
@ -423,7 +424,7 @@
"register_whyRegister": "Perché registrarsi?",
"register_header": "Benvenuto su CryptPad",
"fm_alert_anonymous": "",
"register_writtenPassword": "Ho scritto su carta il mio username e la mia password, procedi",
"register_writtenPassword": "Ho annotato il mio username e la mia password, procedi",
"register_cancel": "Torna indietro",
"register_warning": "Zero Knowledge significa che non possiamo recuperare i tuoi dati se perdi la tua password.",
"register_alreadyRegistered": "Questo utente esiste già, vuoi effettuare il log in?",
@ -442,7 +443,7 @@
"settings_restore": "Importa",
"settings_backupHint2": "Scarica il contenuto corrente dei tuoi pads. I pads saranno scaricati in un formato leggibile, se possibile.",
"settings_backup2": "Scarica il mio CryptDrive",
"settings_backup2Confirm": "Questo scaricherà tutti i tuoi pads e files dal tuo CryptDrive. Se vuoi continuare, scegli un nome e premi OK",
"settings_backup2Confirm": "Questo scaricherà tutti i tuoi pad e files dal tuo CryptDrive. Se vuoi continuare, scegli un nome e premi OK",
"settings_exportTitle": "Esporta il tuo CryptDrive",
"settings_exportDescription": "Per favore attendi mentre scarichiamo e decriptiamo i tuoi documenti. Potrebbe richiedere qualche minuto. Chiudere la finestra interromperà il processo.",
"settings_exportFailed": "Se il pad richiede più di un minuto per essere scaricato, non sarà incluso nell'export. Un link a qualsiasi pad non esportato sarà mostrato.",
@ -457,9 +458,9 @@
"settings_exportErrorEmpty": "",
"settings_exportErrorMissing": "",
"settings_exportErrorOther": "",
"settings_resetNewTitle": "",
"settings_resetNewTitle": "Pulisci CryptDrive",
"settings_resetButton": "",
"settings_reset": "",
"settings_reset": "Rimuovi tutti i file e le cartelle dal tuo CryptDrive",
"settings_resetPrompt": "",
"settings_resetDone": "",
"settings_resetError": "",
@ -482,5 +483,35 @@
"settings_userFeedback": "Abilita feedback dell'user",
"settings_deleteTitle": "Cancella account",
"settings_deleteHint": "La cancellazione dell'account è permanente. Il tuo CryptDrive e la tua lista di pads sarà cancellata dal server. Il resto dei tuoi pads sarà cancellato in 90 giorni se nessun altro li ha salvati nel suo CryptDrive.",
"settings_deleteButton": "Cancella il tuo account"
"settings_deleteButton": "Cancella il tuo account",
"padNotPinnedVariable": "Questo pad scadrà dopo {4} giorni di inattività, {0}accedi{1} o {2}registrati{3} per conservarlo.",
"storageStatus": "Spazio:<br />1<b>2{0}</b>3 usati di <b>4{1}</b>5",
"uploadFolderButton": "Cartella upload",
"fm_morePads": "Altro",
"fc_color": "Cambia colore",
"fc_openInCode": "Apri nell'editor di codice",
"fc_expandAll": "Espandi tutto",
"fc_collapseAll": "Comprimi tutto",
"register_emailWarning0": "Sembra che tu abbia inserito la tua email come nome utente.",
"settings_driveDuplicateHint": "Quando sposti i tuoi pad in una cartella condivisa, una copia è conservata nel tuo CryptDrive per assicurartene il controllo. Puoi nascondere i file duplicati. Soltanto la versione condivisa verrà mostrata, a meno che non venga eliminata: in tal caso il file originale verrà mostrato nella sua posizione precedente.",
"settings_codeIndentation": "Indentazione dell'editor di codice (spazi)",
"settings_codeFontSize": "Dimensione del testo dell'editor di codice",
"uploadFolder_modal_filesPassword": "Password dei file",
"uploadFolder_modal_owner": "File posseduti",
"uploadFolder_modal_forceSave": "Conserva i file nel tuo CryptDrive",
"upload_mustLogin": "Devi eseguire l'accesso per poter caricare file",
"pad_base64": "Questo pad contiene immagini conservate in maniera inefficiente. Queste immagini aumenteranno significativamente le dimensioni del pad nel tuo CryptDrive, e lo renderanno più lento da caricare. Puoi convertire questi file in un nuovo formato che verrà conservato separatamente nel tuo CryptDrive. Vuoi convertire queste immagini ora?",
"mdToolbar_code": "Codice",
"home_host": "Questa è un'istanza di CryptPad gestita indipendentemente dalla community. Il suo codice sorgente è disponibile <a href=\"https://github.com/xwiki-labs/cryptpad\" target=\"_blank\" rel=\"noreferrer noopener\">su GitHub</a>2.",
"whatis_drive_p3": "Puoi anche caricare file nel tuo CryptDrive e condividerli coi colleghi. I file caricati possono essere organizzati proprio come i pad collaborativi.",
"policy_choices_open": "Il nostro codice sorgente è open source, così hai sempre la possibilità di ospitare la tua personale istanza di CryptPad.",
"features_f_file0": "Apri file",
"help": {
"slide": {
"markdown": "Scrivi diapositive in <a href=\"http://www.markdowntutorial.com/\">Markdown</a> e separale con una linea contente <code>---</code>"
}
},
"readme_cat3_l1": "Con l'editor di codice di CryptPad, puoi collaborare su linguaggi di programmazione come Javascript e linguaggi di markup come HTML o Markdown",
"settings_codeSpellcheckLabel": "Abilita la revisione ortografica nell'editor di codice",
"team_inviteLinkError": "Si è verificato un errore durante la creazione del link."
}

View File

@ -35,7 +35,7 @@
"inactiveError": "This pad has been deleted due to inactivity. Press Esc to create a new pad.",
"chainpadError": "A critical error occurred when updating your content. This page is in read-only mode to make sure you won't lose your work.<br>Hit <em>Esc</em> to continue to view this pad, or reload to try editing again.",
"invalidHashError": "The document you've requested has an invalid URL.",
"errorCopy": " You can still copy the content to another location by pressing <em>Esc</em>.<br>Once you leave this page, it will disappear forever!",
"errorCopy": " You can still access the content by pressing <em>Esc</em>.<br>Once you close this window you will not be able to access it again.",
"errorRedirectToHome": "Press <em>Esc</em> to be redirected to your CryptDrive.",
"newVersionError": "A new version of CryptPad is available.<br><a href='#'>Reload</a> to use the new version, or press escape to access your content in <b>offline mode</b>.",
"loading": "Loading...",
@ -1281,5 +1281,11 @@
"team_cat_link": "Invitation Link",
"team_links": "Invitation Links",
"team_inviteInvalidLinkError": "This invitation link is not valid.",
"team_inviteLinkError": "There was an error while creating the link."
"team_inviteLinkError": "There was an error while creating the link.",
"burnAfterReading_linkBurnAfterReading": "View once and self-destruct",
"burnAfterReading_warningLink": "You have set this pad to self-destruct. Once a recipient visits this link, they will be able to see the pad once before it is permanently deleted.",
"burnAfterReading_generateLink": "Click on the button below to generate a link.",
"burnAfterReading_warningAccess": "This document will self-destruct. When you click the button below you will see the content once before it is permanently deleted. When you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later.",
"burnAfterReading_proceed": "view and delete",
"burnAfterReading_warningDeleted": "This pad has been permanently deleted, once you close this window you will not be able to access it again."
}