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

pull/1/head
ansuz 5 years ago
commit 24b4931816

@ -23,5 +23,26 @@
display: flex; display: flex;
flex-flow: column; flex-flow: column;
} }
.cp-support-list-actions {
margin: 10px 0px 10px 2px;
}
.cp-support-list-message {
&:last-child:not(.cp-support-fromadmin) {
color: @colortheme_cp-red;
background-color: lighten(@colortheme_cp-red, 25%);
.cp-support-showdata {
background-color: lighten(@colortheme_cp-red, 30%);
}
}
}
.cp-support-fromadmin {
color: @colortheme_logo-2;
background-color: #FFF;
.cp-support-message-content {
color: @colortheme_logo-2;
}
}
} }

@ -60,6 +60,21 @@ var factory = function (Util, Crypto, Nacl) {
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass; return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
} }
}; };
Hash.getHiddenHashFromKeys = function (type, secret, opts) {
var mode = ((secret.keys && secret.keys.editKeyStr) || secret.key) ? 'edit/' : 'view/';
var pass = secret.password ? 'p/' : '';
if (secret.keys && secret.keys.fileKeyStr) { mode = ''; }
var hash = '/3/' + type + '/' + mode + secret.channel + '/' + pass;
var hashData = Hash.parseTypeHash(type, hash);
if (hashData && hashData.getHash) {
return hashData.getHash(opts || {});
}
return hash;
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) { var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
var version = secret.version; var version = secret.version;
var data = secret.keys; var data = secret.keys;
@ -160,12 +175,28 @@ Version 1
}; };
var parseTypeHash = Hash.parseTypeHash = function (type, hash) { var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; } if (!hash) { return; }
var options; var options = [];
var parsed = {}; var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/'); var hashArr = fixDuplicateSlashes(hash).split('/');
var addOptions = function () {
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.ownerKey = getOwnerKey(options);
};
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad'; parsed.type = 'pad';
parsed.getHash = function () { return hash; }; parsed.getHash = function () { return hash; };
parsed.getOptions = function () {
return {
embed: parsed.embed,
present: parsed.present,
ownerKey: parsed.ownerKey,
password: parsed.password
};
};
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash // Old hash
parsed.channel = hash.slice(0, 32); parsed.channel = hash.slice(0, 32);
@ -173,6 +204,18 @@ Version 1
parsed.version = 0; parsed.version = 0;
return parsed; return parsed;
} }
// Version >= 1: more hash options
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
if (owner) { hash += owner + '/'; }
if (parsed.password || opts.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
if (hashArr[1] && hashArr[1] === '1') { // Version 1 if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1; parsed.version = 1;
parsed.mode = hashArr[2]; parsed.mode = hashArr[2];
@ -180,18 +223,8 @@ Version 1
parsed.key = Crypto.b64AddSlashes(hashArr[4]); parsed.key = Crypto.b64AddSlashes(hashArr[4]);
options = hashArr.slice(5); options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1; addOptions();
parsed.embed = options.indexOf('embed') !== -1;
parsed.ownerKey = getOwnerKey(options);
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
if (owner) { hash += owner + '/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed; return parsed;
} }
if (hashArr[1] && hashArr[1] === '2') { // Version 2 if (hashArr[1] && hashArr[1] === '2') { // Version 2
@ -201,20 +234,19 @@ Version 1
parsed.key = hashArr[4]; parsed.key = hashArr[4];
options = hashArr.slice(5); options = hashArr.slice(5);
parsed.password = options.indexOf('p') !== -1; addOptions();
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1; return parsed;
parsed.ownerKey = getOwnerKey(options); }
if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
parsed.getHash = function (opts) { parsed.version = 3;
var hash = hashArr.slice(0, 5).join('/') + '/'; parsed.app = hashArr[2];
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; parsed.mode = hashArr[3];
if (owner) { hash += owner + '/'; } parsed.channel = hashArr[4];
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; } options = hashArr.slice(5);
if (opts.present) { hash += 'present/'; } addOptions();
return hash;
};
return parsed; return parsed;
} }
return parsed; return parsed;
@ -222,34 +254,54 @@ Version 1
parsed.getHash = function () { return hashArr.join('/'); }; parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) { if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file'; parsed.type = 'file';
parsed.getOptions = function () {
return {
embed: parsed.embed,
present: parsed.present,
ownerKey: parsed.ownerKey,
password: parsed.password
};
};
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey;
if (owner) { hash += owner + '/'; }
if (parsed.password || opts.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
if (hashArr[1] && hashArr[1] === '1') { if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1; parsed.version = 1;
parsed.channel = hashArr[2].replace(/-/g, '/'); parsed.channel = hashArr[2].replace(/-/g, '/');
parsed.key = hashArr[3].replace(/-/g, '/'); parsed.key = hashArr[3].replace(/-/g, '/');
options = hashArr.slice(4); options = hashArr.slice(4);
parsed.ownerKey = getOwnerKey(options); addOptions();
return parsed; return parsed;
} }
if (hashArr[1] && hashArr[1] === '2') { // Version 2 if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2; parsed.version = 2;
parsed.app = hashArr[2]; parsed.app = hashArr[2];
parsed.key = hashArr[3]; parsed.key = hashArr[3];
options = hashArr.slice(4); options = hashArr.slice(4);
parsed.password = options.indexOf('p') !== -1; addOptions();
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1; return parsed;
parsed.ownerKey = getOwnerKey(options); }
parsed.getHash = function (opts) { if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
var hash = hashArr.slice(0, 4).join('/') + '/'; parsed.version = 3;
var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; parsed.app = hashArr[2];
if (owner) { hash += owner + '/'; } parsed.channel = hashArr[3];
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; } options = hashArr.slice(4);
if (opts.present) { hash += 'present/'; } addOptions();
return hash;
};
return parsed; return parsed;
} }
return parsed; return parsed;
@ -303,6 +355,10 @@ Version 1
url += '#' + hash; url += '#' + hash;
return url; return url;
}; };
ret.getOptions = function () {
if (!ret.hashData || !ret.hashData.getOptions) { return {}; }
return ret.hashData.getOptions();
};
if (!/^https*:\/\//.test(href)) { if (!/^https*:\/\//.test(href)) {
idx = href.indexOf('/#'); idx = href.indexOf('/#');
@ -325,6 +381,14 @@ Version 1
return ret; return ret;
}; };
Hash.hashToHref = function (hash, type) {
return '/' + type + '/#' + hash;
};
Hash.hrefToHash = function (href) {
var parsed = Hash.parsePadUrl(href);
return parsed.hash;
};
Hash.getRelativeHref = function (href) { Hash.getRelativeHref = function (href) {
if (!href) { return; } if (!href) { return; }
if (href.indexOf('#') === -1) { return; } if (href.indexOf('#') === -1) { return; }
@ -345,7 +409,7 @@ Version 1
secret.version = 2; secret.version = 2;
secret.type = type; secret.type = type;
}; };
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) { if (!secretHash) {
generate(); generate();
return secret; return secret;
} else { } else {
@ -355,12 +419,7 @@ Version 1
if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); } if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); }
parsed = parseTypeHash(type, secretHash); parsed = parseTypeHash(type, secretHash);
hash = secretHash; hash = secretHash;
} else {
var pHref = parsePadUrl(window.location.href);
parsed = pHref.hashData;
hash = pHref.hash;
} }
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) { if (hash.length === 0) {
generate(); generate();
return secret; return secret;
@ -496,8 +555,8 @@ Version 1
if (typeof(parsed.hashData.version) === "undefined") { return; } if (typeof(parsed.hashData.version) === "undefined") { return; }
// pads and files should have a base64 (or hex) key // pads and files should have a base64 (or hex) key
if (parsed.hashData.type === 'pad' || parsed.hashData.type === 'file') { if (parsed.hashData.type === 'pad' || parsed.hashData.type === 'file') {
if (!parsed.hashData.key) { return; } if (!parsed.hashData.key && !parsed.hashData.channel) { return; }
if (!/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; } if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; }
} }
} }
return true; return true;

@ -1104,5 +1104,36 @@ define([
}; };
}; };
UI.makeSpinner = function ($container) {
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide();
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide();
var spin = function () {
$ok.hide();
$spinner.show();
};
var hide = function () {
$ok.hide();
$spinner.hide();
};
var done = function () {
$ok.show();
$spinner.hide();
};
if ($container && $container.append) {
$container.append($ok);
$container.append($spinner);
}
return {
ok: $ok[0],
spinner: $spinner[0],
spin: spin,
hide: hide,
done: done
};
};
return UI; return UI;
}); });

@ -4602,10 +4602,10 @@ define([
var f = priv.friends[curve]; var f = priv.friends[curve];
$verified.append(h('span.fa.fa-certificate')); $verified.append(h('span.fa.fa-certificate'));
var $avatar = $(h('span.cp-avatar')).appendTo($verified); var $avatar = $(h('span.cp-avatar')).appendTo($verified);
$verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName]))); $verified.append(h('p', Messages._getKey('isContact', [f.displayName])));
common.displayAvatar($avatar, f.avatar, f.displayName); common.displayAvatar($avatar, f.avatar, f.displayName);
} else { } else {
$verified.append(Messages._getKey('requestEdit_fromStranger', [name])); $verified.append(Messages._getKey('isNotContact', [name]));
} }
return verified; return verified;
}; };

@ -49,6 +49,12 @@ define([
account: {}, account: {},
}; };
// Store the href in memory
// This is a placeholder value overriden in common.ready from sframe-common-outer
var currentPad = common.currentPad = {
href: window.location.href
};
// COMMON // COMMON
common.getLanguage = function () { common.getLanguage = function () {
return Messages._languageUsed; return Messages._languageUsed;
@ -374,7 +380,7 @@ define([
common.getMetadata = function (cb) { common.getMetadata = function (cb) {
var parsed = Hash.parsePadUrl(window.location.href); var parsed = Hash.parsePadUrl(currentPad.href);
postMessage("GET_METADATA", parsed && parsed.type, function (obj) { postMessage("GET_METADATA", parsed && parsed.type, function (obj) {
if (obj && obj.error) { return void cb(obj.error); } if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj); cb(null, obj);
@ -394,7 +400,7 @@ define([
common.setPadAttribute = function (attr, value, cb, href) { common.setPadAttribute = function (attr, value, cb, href) {
cb = cb || function () {}; cb = cb || function () {};
href = Hash.getRelativeHref(href || window.location.href); href = Hash.getRelativeHref(href || currentPad.href);
postMessage("SET_PAD_ATTRIBUTE", { postMessage("SET_PAD_ATTRIBUTE", {
href: href, href: href,
attr: attr, attr: attr,
@ -405,7 +411,7 @@ define([
}); });
}; };
common.getPadAttribute = function (attr, cb, href) { common.getPadAttribute = function (attr, cb, href) {
href = Hash.getRelativeHref(href || window.location.href); href = Hash.getRelativeHref(href || currentPad.href);
if (!href) { if (!href) {
return void cb('E404'); return void cb('E404');
} }
@ -505,7 +511,7 @@ define([
}; };
common.saveAsTemplate = function (Cryptput, data, cb) { common.saveAsTemplate = function (Cryptput, data, cb) {
var p = Hash.parsePadUrl(window.location.href); var p = Hash.parsePadUrl(currentPad.href);
if (!p.type) { return; } if (!p.type) { return; }
// PPP: password for the new template? // PPP: password for the new template?
var hash = Hash.createRandomHash(p.type); var hash = Hash.createRandomHash(p.type);
@ -543,7 +549,7 @@ define([
var href = data.href; var href = data.href;
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var parsed2 = Hash.parsePadUrl(window.location.href); var parsed2 = Hash.parsePadUrl(currentPad.href);
if(!parsed) { throw new Error("Cannot get template hash"); } if(!parsed) { throw new Error("Cannot get template hash"); }
postMessage("INCREMENT_TEMPLATE_USE", href); postMessage("INCREMENT_TEMPLATE_USE", href);
@ -601,7 +607,7 @@ define([
var fileHost = Config.fileHost || window.location.origin; var fileHost = Config.fileHost || window.location.origin;
var data = common.fromFileData; var data = common.fromFileData;
var parsed = Hash.parsePadUrl(data.href); var parsed = Hash.parsePadUrl(data.href);
var parsed2 = Hash.parsePadUrl(window.location.href); var parsed2 = Hash.parsePadUrl(currentPad.href);
var hash = parsed.hash; var hash = parsed.hash;
var name = data.title; var name = data.title;
var secret = Hash.getSecrets('file', hash, data.password); var secret = Hash.getSecrets('file', hash, data.password);
@ -660,7 +666,7 @@ define([
// Forget button // Forget button
common.moveToTrash = function (cb, href) { common.moveToTrash = function (cb, href) {
href = href || window.location.href; href = href || currentPad.href;
postMessage("MOVE_TO_TRASH", { href: href }, cb); postMessage("MOVE_TO_TRASH", { href: href }, cb);
}; };
@ -668,7 +674,7 @@ define([
common.setPadTitle = function (data, cb) { common.setPadTitle = function (data, cb) {
if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); } if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); }
var href = data.href || window.location.href; var href = data.href || currentPad.href;
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
if (!parsed.hash) { return cb ('Invalid hash'); } if (!parsed.hash) { return cb ('Invalid hash'); }
data.href = parsed.getUrl({present: parsed.present}); data.href = parsed.getUrl({present: parsed.present});
@ -698,7 +704,7 @@ define([
if (obj.error !== "EAUTH") { console.log("unable to set pad title"); } if (obj.error !== "EAUTH") { console.log("unable to set pad title"); }
return void cb(obj.error); return void cb(obj.error);
} }
cb(); cb(null, obj);
}); });
}; };
@ -755,6 +761,13 @@ define([
cb(void 0, data); cb(void 0, data);
}); });
}; };
// Get data about a given channel: use with hidden hashes
common.getPadDataFromChannel = function (obj, cb) {
if (!obj || !obj.channel) { return void cb('EINVAL'); }
postMessage("GET_PAD_DATA_FROM_CHANNEL", obj, function (data) {
cb(void 0, data);
});
};
// Admin // Admin
@ -832,6 +845,7 @@ define([
pad.onConnectEvent = Util.mkEvent(); pad.onConnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent();
pad.onMetadataEvent = Util.mkEvent(); pad.onMetadataEvent = Util.mkEvent();
pad.onChannelDeleted = Util.mkEvent();
pad.requestAccess = function (data, cb) { pad.requestAccess = function (data, cb) {
postMessage("REQUEST_PAD_ACCESS", data, cb); postMessage("REQUEST_PAD_ACCESS", data, cb);
@ -1608,7 +1622,7 @@ define([
hashes = Hash.getHashes(secret); hashes = Hash.getHashes(secret);
return void cb(null, hashes); return void cb(null, hashes);
} }
var parsed = Hash.parsePadUrl(window.location.href); var parsed = Hash.parsePadUrl(currentPad.href);
if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); }
hashes = Hash.getHashes(secret); hashes = Hash.getHashes(secret);
@ -1679,7 +1693,7 @@ define([
LocalStore.logout(); LocalStore.logout();
// redirect them to log in, and come back when they're done. // redirect them to log in, and come back when they're done.
sessionStorage.redirectTo = window.location.href; sessionStorage.redirectTo = currentPad.href;
window.location.href = '/login/'; window.location.href = '/login/';
}; };
@ -1740,6 +1754,7 @@ define([
PAD_CONNECT: common.padRpc.onConnectEvent.fire, PAD_CONNECT: common.padRpc.onConnectEvent.fire,
PAD_ERROR: common.padRpc.onErrorEvent.fire, PAD_ERROR: common.padRpc.onErrorEvent.fire,
PAD_METADATA: common.padRpc.onMetadataEvent.fire, PAD_METADATA: common.padRpc.onMetadataEvent.fire,
CHANNEL_DELETED: common.padRpc.onChannelDeleted.fire,
// Drive // Drive
DRIVE_LOG: common.drive.onLog.fire, DRIVE_LOG: common.drive.onLog.fire,
DRIVE_CHANGE: common.drive.onChange.fire, DRIVE_CHANGE: common.drive.onChange.fire,
@ -1780,6 +1795,11 @@ define([
return function (f, rdyCfg) { return function (f, rdyCfg) {
rdyCfg = rdyCfg || {}; rdyCfg = rdyCfg || {};
if (rdyCfg.currentPad) {
currentPad = common.currentPad = rdyCfg.currentPad;
}
if (initialized) { if (initialized) {
return void setTimeout(function () { f(void 0, env); }); return void setTimeout(function () { f(void 0, env); });
} }
@ -1878,7 +1898,7 @@ define([
anonHash: LocalStore.getFSHash(), anonHash: LocalStore.getFSHash(),
localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), // TODO move this to LocalStore ? localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), // TODO move this to LocalStore ?
language: common.getLanguage(), language: common.getLanguage(),
driveEvents: rdyCfg.driveEvents // Boolean driveEvents: true //rdyCfg.driveEvents // Boolean
}; };
// if a pad is created from a file // if a pad is created from a file
if (sessionStorage[Constants.newPadFileData]) { if (sessionStorage[Constants.newPadFileData]) {

@ -55,7 +55,6 @@ define([
var a = h('a.cp-md-toc-link', { var a = h('a.cp-md-toc-link', {
href: '#', href: '#',
'data-href': obj.id, 'data-href': obj.id,
title: obj.title
}); });
a.innerHTML = obj.title; a.innerHTML = obj.title;
content.push(h('p.cp-md-toc-'+level, ['• ', a])); content.push(h('p.cp-md-toc-'+level, ['• ', a]));

@ -1029,15 +1029,28 @@ define([
return ret; return ret;
}; };
var openFile = function (el, href) { var openFile = function (el, isRo) {
if (!href) { var data = manager.getFileData(el);
var data = manager.getFileData(el); if (!data || (!data.href && !data.roHref)) {
if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data);
return void logError("Missing data for the file", el, data); }
} var href = isRo ? data.roHref : (data.href || data.roHref);
href = data.href || data.roHref; var priv = metadataMgr.getPrivateData();
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
if (useUnsafe) {
return void window.open(APP.origin + href);
} }
window.open(APP.origin + href);
// Get hidden hash
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
if (isRo && secret.keys && secret.keys.editKeyStr) {
delete secret.keys.editKeyStr;
delete secret.key;
}
var hash = Hash.getHiddenHashFromKeys(parsed.type, secret);
var hiddenHref = Hash.hashToHref(hash, parsed.type);
window.open(APP.origin + hiddenHref);
}; };
var refresh = APP.refresh = function () { var refresh = APP.refresh = function () {
@ -3034,7 +3047,7 @@ define([
$icon.append(getFileIcon(r.id)); $icon.append(getFileIcon(r.id));
$type.text(Messages.type[parsed.type] || parsed.type); $type.text(Messages.type[parsed.type] || parsed.type);
$title.click(function () { $title.click(function () {
openFile(null, r.data.href); openFile(r.id);
}); });
$atimeName.text(Messages.fm_lastAccess); $atimeName.text(Messages.fm_lastAccess);
$atime.text(new Date(r.data.atime).toLocaleString()); $atime.text(new Date(r.data.atime).toLocaleString());
@ -3944,15 +3957,12 @@ define([
// ANON_SHARED_FOLDER // ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder); el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
} }
var href;
if (manager.isPathIn(p.path, [FILES_DATA])) { if (manager.isPathIn(p.path, [FILES_DATA])) {
href = el.roHref; el = p.path[1];
} else { } else {
if (!el || manager.isFolder(el)) { return; } if (!el || manager.isFolder(el)) { return; }
var data = manager.getFileData(el);
href = data.roHref;
} }
openFile(null, href); openFile(el, true);
}); });
} }
else if ($this.hasClass('cp-app-drive-context-openincode')) { else if ($this.hasClass('cp-app-drive-context-openincode')) {

@ -62,6 +62,15 @@ body.cp-app-sheet, body.cp-app-oodoc, body.cp-app-ooslide {
background-color: lightgrey; background-color: lightgrey;
display: flex; display: flex;
flex-flow: column; flex-flow: column;
position: relative;
}
#cp-app-oo-offline {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: rgba(255,255,255,0.5);
} }
#ooframe { #ooframe {
flex: 1; flex: 1;

@ -52,10 +52,10 @@ define([
$: $ $: $
}; };
var CHECKPOINT_INTERVAL = 50; var CHECKPOINT_INTERVAL = 50;
var DISPLAY_RESTORE_BUTTON = false; var DISPLAY_RESTORE_BUTTON = false;
var NEW_VERSION = 2; var NEW_VERSION = 2;
var PENDING_TIMEOUT = 30000;
var debug = function (x) { var debug = function (x) {
if (!window.CP_DEV_MODE) { return; } if (!window.CP_DEV_MODE) { return; }
@ -76,6 +76,7 @@ define([
var privateData = metadataMgr.getPrivateData(); var privateData = metadataMgr.getPrivateData();
var readOnly = false; var readOnly = false;
var offline = false; var offline = false;
var pendingChanges = {};
var config = {}; var config = {};
var content = { var content = {
hashes: {}, hashes: {},
@ -89,6 +90,7 @@ define([
var myUniqueOOId; var myUniqueOOId;
var myOOId; var myOOId;
var sessionId = Hash.createChannelId(); var sessionId = Hash.createChannelId();
var cpNfInner;
// This structure is used for caching media data and blob urls for each media cryptpad url // This structure is used for caching media data and blob urls for each media cryptpad url
var mediasData = {}; var mediasData = {};
@ -102,6 +104,18 @@ define([
return metadataMgr.getNetfluxId() + '-' + privateData.clientId; return metadataMgr.getNetfluxId() + '-' + privateData.clientId;
}; };
var setEditable = function (state) {
$('#cp-app-oo-editor').find('#cp-app-oo-offline').remove();
try {
window.frames[0].editor.asc_setViewMode(!state);
//window.frames[0].editor.setViewModeDisconnect(true);
} catch (e) {}
if (!state) {
$('#cp-app-oo-editor').append(h('div#cp-app-oo-offline'));
}
debug(state);
};
var deleteOffline = function () { var deleteOffline = function () {
var ids = content.ids; var ids = content.ids;
var users = Object.keys(metadataMgr.getMetadata().users); var users = Object.keys(metadataMgr.getMetadata().users);
@ -573,15 +587,23 @@ define([
var myId = getId(); var myId = getId();
content.locks[myId] = msg; content.locks[myId] = msg;
oldLocks = JSON.parse(JSON.stringify(content.locks)); oldLocks = JSON.parse(JSON.stringify(content.locks));
// Answer to our onlyoffice
send({
type: "getLock",
locks: getLock()
});
// Remove old locks // Remove old locks
deleteOfflineLocks(); deleteOfflineLocks();
// Prepare callback
if (cpNfInner) {
var onPatchSent = function () {
cpNfInner.offPatchSent(onPatchSent);
// Answer to our onlyoffice
send({
type: "getLock",
locks: getLock()
});
};
cpNfInner.onPatchSent(onPatchSent);
}
// Commit // Commit
APP.onLocal(); APP.onLocal();
APP.realtime.sync();
}; };
var parseChanges = function (changes) { var parseChanges = function (changes) {
@ -600,13 +622,30 @@ define([
}; };
}); });
}; };
var handleChanges = function (obj, send) { var handleChanges = function (obj, send) {
// Allow the changes // Add a new entry to the pendingChanges object.
send({ // If we can't send the patch within 30s, force a page reload
type: "unSaveLock", var uid = Util.uid();
index: ooChannel.cpIndex, pendingChanges[uid] = setTimeout(function () {
time: +new Date() // If we're offline, force a reload on reconnect
}); if (offline) {
pendingChanges.force = true;
return;
}
// We're online: force a reload now
setEditable(false);
UI.alert(Messages.realtime_unrecoverableError, function () {
common.gotoURL();
});
}, PENDING_TIMEOUT);
if (offline) {
pendingChanges.force = true;
return;
}
// Send the changes // Send the changes
rtChannel.sendMsg({ rtChannel.sendMsg({
type: "saveChanges", type: "saveChanges",
@ -615,7 +654,22 @@ define([
locks: [content.locks[getId()]], locks: [content.locks[getId()]],
excelAdditionalInfo: null excelAdditionalInfo: null
}, null, function (err, hash) { }, null, function (err, hash) {
if (err) { return void console.error(err); } if (err) {
return void console.error(err);
}
if (pendingChanges[uid]) {
clearTimeout(pendingChanges[uid]);
delete pendingChanges[uid];
}
// Call unSaveLock to tell onlyoffice that the patch was sent.
// It will allow you to make changes to another cell.
// If there is an error and unSaveLock is not called, onlyoffice
// will try to send the patch again
send({
type: "unSaveLock",
index: ooChannel.cpIndex,
time: +new Date()
});
// Increment index and update latest hash // Increment index and update latest hash
ooChannel.cpIndex++; ooChannel.cpIndex++;
ooChannel.lastHash = hash; ooChannel.lastHash = hash;
@ -659,10 +713,12 @@ define([
break; break;
case "isSaveLock": case "isSaveLock":
// TODO ping the server to check if we're online first? // TODO ping the server to check if we're online first?
send({ if (!offline) {
type: "saveLock", send({
saveLock: false type: "saveLock",
}); saveLock: false
});
}
break; break;
case "getLock": case "getLock":
handleLock(obj, send); handleLock(obj, send);
@ -748,7 +804,9 @@ define([
}, },
"events": { "events": {
"onAppReady": function(/*evt*/) { "onAppReady": function(/*evt*/) {
var $tb = $('iframe[name="frameEditor"]').contents().find('head'); var $iframe = $('iframe[name="frameEditor"]').contents();
$iframe.prop('tabindex', '-1');
var $tb = $iframe.find('head');
var css = // Old OO var css = // Old OO
'#id-toolbar-full .toolbar-group:nth-child(2), #id-toolbar-full .separator:nth-child(3) { display: none; }' + '#id-toolbar-full .toolbar-group:nth-child(2), #id-toolbar-full .separator:nth-child(3) { display: none; }' +
'#fm-btn-save { display: none !important; }' + '#fm-btn-save { display: none !important; }' +
@ -1283,7 +1341,6 @@ define([
var initializing = true; var initializing = true;
var $bar = $('#cp-toolbar'); var $bar = $('#cp-toolbar');
var cpNfInner;
config = { config = {
patchTransformer: ChainPad.SmartJSONTransformer, patchTransformer: ChainPad.SmartJSONTransformer,
@ -1300,15 +1357,6 @@ define([
} }
}; };
var setEditable = function (state) {
if (!state) {
try {
window.frames[0].editor.setViewModeDisconnect(true);
} catch (e) {}
}
debug(state);
};
var stringifyInner = function () { var stringifyInner = function () {
var obj = { var obj = {
content: content, content: content,
@ -1398,11 +1446,20 @@ define([
var $exportXLSX = common.createButton('export', true, {}, exportXLSXFile); var $exportXLSX = common.createButton('export', true, {}, exportXLSXFile);
$exportXLSX.appendTo($rightside); $exportXLSX.appendTo($rightside);
var type = common.getMetadataMgr().getPrivateData().ooType;
var accept = [".bin", ".ods", ".xlsx"]; var accept = [".bin", ".ods", ".xlsx"];
if (type === "ooslide") {
accept = ['.bin', '.odp', '.pptx'];
} else if (type === "oodoc") {
accept = ['.bin', '.odt', '.docx'];
}
if (typeof(Atomics) === "undefined") { if (typeof(Atomics) === "undefined") {
accept = ['.bin']; accept = ['.bin'];
} }
var $importXLSX = common.createButton('import', true, { accept: accept, binary : ["ods", "xlsx"] }, importXLSXFile); var $importXLSX = common.createButton('import', true, {
accept: accept,
binary : ["ods", "xlsx", "odt", "docx", "odp", "pptx"]
}, importXLSXFile);
$importXLSX.appendTo($rightside); $importXLSX.appendTo($rightside);
if (common.isLoggedIn()) { if (common.isLoggedIn()) {
@ -1557,14 +1614,19 @@ define([
}; };
config.onConnectionChange = function (info) { config.onConnectionChange = function (info) {
setEditable(info.state);
if (info.state) { if (info.state) {
// If we tried to send changes while we were offline, force a page reload
UI.findOKButton().click(); UI.findOKButton().click();
UI.confirm(Messages.oo_reconnect, function (yes) { if (Object.keys(pendingChanges).length) {
if (!yes) { return; } return void UI.confirm(Messages.oo_reconnect, function (yes) {
common.gotoURL(); if (!yes) { return; }
}); common.gotoURL();
});
}
setEditable(true);
offline = false;
} else { } else {
setEditable(false);
offline = true; offline = true;
UI.findOKButton().click(); UI.findOKButton().click();
UI.alert(Messages.common_connectionLost, undefined, true); UI.alert(Messages.common_connectionLost, undefined, true);

@ -9,6 +9,7 @@ define([
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
var hash, href;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -19,6 +20,13 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
@ -144,6 +152,8 @@ define([
}); });
}; };
SFCommonO.start({ SFCommonO.start({
hash: hash,
href: href,
type: 'oo', type: 'oo',
useCreationScreen: true, useCreationScreen: true,
addData: addData, addData: addData,

@ -1016,8 +1016,12 @@ define([
if (title.trim() === "") { title = UserObject.getDefaultName(p); } if (title.trim() === "") { title = UserObject.getDefaultName(p); }
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } if (AppConfig.disableAnonymousStore && !store.loggedIn) {
if (p.type === "debug") { return void cb(); } return void cb({ notStored: true });
}
if (p.type === "debug") {
return void cb({ notStored: true });
}
var channelData = Store.channels && Store.channels[channel]; var channelData = Store.channels && Store.channels[channel];
@ -1108,7 +1112,7 @@ define([
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", { postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
autoStore: autoStore autoStore: autoStore
}); });
return void cb(); return void cb({ notStored: true });
} else { } else {
var roHref; var roHref;
if (h.mode === "view") { if (h.mode === "view") {
@ -1187,7 +1191,9 @@ define([
}); });
cb(list); cb(list);
}; };
// Get the first pad we can find in any of our managers and return its file data
// Get the first pad we can find in any of our drives and return its file data
// NOTE: This is currently only used for template: this won't search inside shared folders
Store.getPadData = function (clientId, id, cb) { Store.getPadData = function (clientId, id, cb) {
var res = {}; var res = {};
getAllStores().some(function (s) { getAllStores().some(function (s) {
@ -1199,6 +1205,49 @@ define([
cb(res); cb(res);
}; };
Store.getPadDataFromChannel = function (clientId, obj, cb) {
var channel = obj.channel;
var edit = obj.edit;
var isFile = obj.file;
var res;
var viewRes;
getAllStores().some(function (s) {
var chans = s.manager.findChannel(channel);
if (!Array.isArray(chans)) { return; }
return chans.some(function (pad) {
if (!pad || !pad.data) { return; }
var data = pad.data;
// We've found a match: return the value and stop the loops
if ((edit && data.href) || (!edit && data.roHref) || isFile) {
res = data;
return true;
}
// We've found a weaker match: store it for now
if (edit && !viewRes && data.roHref) {
viewRes = data;
}
});
});
// Call back with the best value we can get
cb(res || viewRes || {});
};
// Hidden hash: if a pad is deleted, we may have to switch back to full hash
// in some tabs
Store.checkDeletedPad = function (channel) {
if (!channel) { return; }
// Check if the pad is still stored in one of our drives
Store.getPadDataFromChannel(null, {
channel: channel,
isFile: true // we don't care if it's view or edit
}, function (res) {
// If it is stored, abort
if (Object.keys(res).length) { return; }
// Otherwise, tell all the tabs that this channel was deleted and give them the hrefs
broadcast([], "CHANNEL_DELETED", channel);
});
};
// Messaging (manage friends from the userlist) // Messaging (manage friends from the userlist)
Store.answerFriendRequest = function (clientId, obj, cb) { Store.answerFriendRequest = function (clientId, obj, cb) {
@ -2095,6 +2144,12 @@ define([
} }
} }
} }
if (o && !n && Array.isArray(p) && (p[0] === UserObject.FILES_DATA ||
(p[0] === 'drive' && p[1] === UserObject.FILES_DATA))) {
setTimeout(function () {
Store.checkDeletedPad(o && o.channel);
});
}
sendDriveEvent('DRIVE_CHANGE', { sendDriveEvent('DRIVE_CHANGE', {
id: fId, id: fId,
old: o, old: o,
@ -2228,6 +2283,7 @@ define([
Store.pinPads(null, data, cb); Store.pinPads(null, data, cb);
}; };
if (!proxy.settings) { proxy.settings = {}; } if (!proxy.settings) { proxy.settings = {}; }
if (!proxy.friends_pending) { proxy.friends_pending = {}; }
var manager = store.manager = ProxyManager.create(proxy.drive, { var manager = store.manager = ProxyManager.create(proxy.drive, {
onSync: function (cb) { onSync(null, cb); }, onSync: function (cb) { onSync(null, cb); },
edPublic: proxy.edPublic, edPublic: proxy.edPublic,

@ -53,7 +53,7 @@ define([
// all our client IDs. // all our client IDs.
if (chan.clients) { if (chan.clients) {
chan.clients.forEach(function (cl) { chan.clients.forEach(function (cl) {
if (ctx.clients[cl] && !ctx.clients[cl].id) { if (ctx.clients[cl]) {
ctx.clients[cl].id = wc.myID + '-' + cl; ctx.clients[cl].id = wc.myID + '-' + cl;
} }
}); });
@ -189,15 +189,22 @@ define([
if (!c) { return void cb({ error: 'NOT_IN_CHANNEL' }); } if (!c) { return void cb({ error: 'NOT_IN_CHANNEL' }); }
var chan = ctx.channels[c.channel]; var chan = ctx.channels[c.channel];
if (!chan) { return void cb({ error: 'INVALID_CHANNEL' }); } if (!chan) { return void cb({ error: 'INVALID_CHANNEL' }); }
// Prepare the callback: broadcast the message to the other local tabs
// if the message is sent
var _cb = function (obj) {
if (obj && obj.error) { return void cb(obj); }
ctx.emit('MESSAGE', {
msg: data.msg
}, chan.clients.filter(function (cl) {
return cl !== clientId;
}));
cb();
};
// Send the message
if (data.isCp) { if (data.isCp) {
return void chan.sendMsg(data.isCp, cb); return void chan.sendMsg(data.isCp, _cb);
} }
chan.sendMsg(data.msg, cb); chan.sendMsg(data.msg, _cb);
ctx.emit('MESSAGE', {
msg: data.msg
}, chan.clients.filter(function (cl) {
return cl !== clientId;
}));
}; };
var reencrypt = function (ctx, data, cId, cb) { var reencrypt = function (ctx, data, cId, cb) {

@ -50,6 +50,7 @@ define([
GET_TEMPLATES: Store.getTemplates, GET_TEMPLATES: Store.getTemplates,
GET_SECURE_FILES_LIST: Store.getSecureFilesList, GET_SECURE_FILES_LIST: Store.getSecureFilesList,
GET_PAD_DATA: Store.getPadData, GET_PAD_DATA: Store.getPadData,
GET_PAD_DATA_FROM_CHANNEL: Store.getPadDataFromChannel,
GET_STRONGER_HASH: Store.getStrongerHash, GET_STRONGER_HASH: Store.getStrongerHash,
INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse, INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
GET_SHARED_FOLDER: Store.getSharedFolder, GET_SHARED_FOLDER: Store.getSharedFolder,

@ -93,6 +93,12 @@ define([
} }
} }
} }
if (o && !n && Array.isArray(p) && (p[0] === UserObject.FILES_DATA ||
(p[0] === 'drive' && p[1] === UserObject.FILES_DATA))) {
setTimeout(function () {
ctx.Store.checkDeletedPad(o && o.channel);
});
}
team.sendEvent('DRIVE_CHANGE', { team.sendEvent('DRIVE_CHANGE', {
id: fId, id: fId,
old: o, old: o,

@ -438,14 +438,24 @@ define([
parentEl.push(id); parentEl.push(id);
return; return;
} }
// Add to root if path is ROOT or if no path // Add to root if no path
var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']); var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']);
if (path && exp.isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) { if (filesList.indexOf(id) === -1 && !newPath) {
parentEl = exp.find(newPath || [ROOT]); newPath = [ROOT];
}
// Add to root
if (path && exp.isPathIn(newPath, [ROOT])) {
parentEl = exp.find(newPath);
if (parentEl) { if (parentEl) {
var newName = exp.getAvailableName(parentEl, Hash.createChannelId()); var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
parentEl[newName] = id; parentEl[newName] = id;
return; return;
} else {
parentEl = exp.find([ROOT]);
newPath.slice(1).forEach(function (folderName) {
parentEl = parentEl[folderName] = parentEl[folderName] || {};
});
parentEl[Hash.createChannelId()] = id;
} }
} }
}; };

@ -771,6 +771,9 @@ define([
toUnpin.forEach(function (chan) { toUnpin.forEach(function (chan) {
if (toKeep.indexOf(chan) === -1) { if (toKeep.indexOf(chan) === -1) {
unpinList.push(chan); unpinList.push(chan);
// Check if need need to restore a full hash (hidden hash deleted from drive)
Env.Store.checkDeletedPad(chan);
} }
}); });
@ -783,7 +786,16 @@ define([
}; };
// Empty the trash (main drive only) // Empty the trash (main drive only)
var _emptyTrash = function (Env, data, cb) { var _emptyTrash = function (Env, data, cb) {
Env.user.userObject.emptyTrash(cb); Env.user.userObject.emptyTrash(function (err, toClean) {
cb();
// Check if need need to restore a full hash (hidden hash deleted from drive)
if (!Array.isArray(toClean)) { return; }
var toCheck = Util.deduplicateString(toClean);
toCheck.forEach(function (chan) {
Env.Store.checkDeletedPad(chan);
});
});
}; };
// Rename files or folders // Rename files or folders
var _rename = function (Env, data, cb) { var _rename = function (Env, data, cb) {

@ -8,6 +8,7 @@ define([
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
var hash, href;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -18,6 +19,14 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
@ -36,6 +45,8 @@ define([
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
SFCommonO.start({ SFCommonO.start({
hash: hash,
href: href,
useCreationScreen: true, useCreationScreen: true,
messaging: true messaging: true
}); });

@ -48,6 +48,8 @@ define([
var updateLoadingProgress = config.updateLoadingProgress; var updateLoadingProgress = config.updateLoadingProgress;
config = undefined; config = undefined;
var evPatchSent = Util.mkEvent();
var chainpad = ChainPad.create({ var chainpad = ChainPad.create({
userName: userName, userName: userName,
initialState: initialState, initialState: initialState,
@ -57,7 +59,10 @@ define([
logLevel: logLevel logLevel: logLevel
}); });
chainpad.onMessage(function(message, cb) { chainpad.onMessage(function(message, cb) {
sframeChan.query('Q_RT_MESSAGE', message, cb); sframeChan.query('Q_RT_MESSAGE', message, function (err) {
if (!err) { evPatchSent.fire(); }
cb(err);
});
}); });
chainpad.onPatch(function () { chainpad.onPatch(function () {
onRemote({ realtime: chainpad }); onRemote({ realtime: chainpad });
@ -149,6 +154,8 @@ define([
metadataMgr: metadataMgr, metadataMgr: metadataMgr,
whenRealtimeSyncs: whenRealtimeSyncs, whenRealtimeSyncs: whenRealtimeSyncs,
onInfiniteSpinner: evInfiniteSpinner.reg, onInfiniteSpinner: evInfiniteSpinner.reg,
onPatchSent: evPatchSent.reg,
offPatchSent: evPatchSent.unreg,
chainpad: chainpad, chainpad: chainpad,
}); });
}; };

@ -30,6 +30,12 @@ define([
var password; var password;
var initialPathInDrive; var initialPathInDrive;
var currentPad = window.CryptPad_location = {
app: '',
href: cfg.href || window.location.href,
hash: cfg.hash || window.location.hash
};
nThen(function (waitFor) { nThen(function (waitFor) {
// Load #2, the loading screen is up so grab whatever you need... // Load #2, the loading screen is up so grab whatever you need...
require([ require([
@ -134,8 +140,15 @@ define([
}); });
} }
}), { }), {
driveEvents: cfg.driveEvents driveEvents: cfg.driveEvents,
currentPad: currentPad
}); });
if (window.history && window.history.replaceState && currentPad.hash) {
var nHash = currentPad.hash;
if (!/^#/.test(nHash)) { nHash = '#' + nHash; }
window.history.replaceState({}, window.document.title, nHash);
}
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (!Utils.Hash.isValidHref(window.location.href)) { if (!Utils.Hash.isValidHref(window.location.href)) {
@ -160,6 +173,8 @@ define([
}); });
}); });
var parsed = Utils.Hash.parsePadUrl(currentPad.href);
currentPad.app = parsed.type;
if (cfg.getSecrets) { if (cfg.getSecrets) {
var w = waitFor(); var w = waitFor();
// No password for drive, profile and todo // No password for drive, profile and todo
@ -171,15 +186,25 @@ define([
}); });
})); }));
} else { } else {
var parsed = Utils.Hash.parsePadUrl(window.location.href);
var todo = function () { var todo = function () {
secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, void 0, password); secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, password);
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { Cryptpad.getShareHashes(secret, waitFor(function (err, h) {
hashes = h; hashes = h;
// Update the rendered hash and the full hash with the "password" settings
if (password && !parsed.hashData.password) { if (password && !parsed.hashData.password) {
var opts = parsed.getOptions();
opts.password = true;
// Full hash
currentPad.href = parsed.getUrl(opts);
if (parsed.hashData) {
currentPad.hash = parsed.hashData.getHash(opts);
}
// Rendered (maybe hidden) hash
var renderedParsed = Utils.Hash.parsePadUrl(window.location.href);
var ohc = window.onhashchange; var ohc = window.onhashchange;
window.onhashchange = function () {}; window.onhashchange = function () {};
window.location.hash = h.fileHash || h.editHash || h.viewHash || window.location.hash; window.location.href = renderedParsed.getUrl(opts);
window.onhashchange = ohc; window.onhashchange = ohc;
ohc({reset: true}); ohc({reset: true});
} }
@ -241,13 +266,13 @@ define([
if (parsed.type === "file") { if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel) // `isNewChannel` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata // `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(window.location.href, password, function (e, size) { Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
next(e, size === 0); next(e, size === 0);
}); });
return; return;
} }
// Not a file, so we can use `isNewChannel` // Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(window.location.href, password, next); Cryptpad.isNewChannel(currentPad.href, password, next);
}); });
sframeChan.event("EV_PAD_PASSWORD", cfg); sframeChan.event("EV_PAD_PASSWORD", cfg);
}; };
@ -257,7 +282,47 @@ define([
var passwordCfg = { var passwordCfg = {
value: '' value: ''
}; };
// Hidden hash: can't find the channel in our drives: abort
var noPadData = function (err) {
sframeChan.event("EV_PAD_NODATA", err);
};
var newHref;
nThen(function (w) { nThen(function (w) {
if (parsed.hashData.key || !parsed.hashData.channel) { return; }
var edit = parsed.hashData.mode === 'edit';
Cryptpad.getPadDataFromChannel({
channel: parsed.hashData.channel,
edit: edit,
file: parsed.hashData.type === 'file'
}, w(function (err, res) {
// Error while getting data? abort
if (err || !res || res.error) {
w.abort();
return void noPadData(err || (!res ? 'EINVAL' : res.error));
}
// No data found? abort
if (!Object.keys(res).length) {
w.abort();
return void noPadData('NO_RESULT');
}
// Data found but weaker? warn
if (edit && !res.href) {
newHref = res.roHref;
}
// We have good data, keep the hash in memory
newHref = edit ? res.href : (res.roHref || res.href);
}));
}).nThen(function (w) {
if (newHref) {
// Get the options (embed, present, etc.) of the hidden hash
// Use the same options in the full hash
var opts = parsed.getOptions();
parsed = Utils.Hash.parsePadUrl(newHref);
currentPad.href = parsed.getUrl(opts);
currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts);
}
Cryptpad.getPadAttribute('title', w(function (err, data) { Cryptpad.getPadAttribute('title', w(function (err, data) {
stored = (!err && typeof (data) === "string"); stored = (!err && typeof (data) === "string");
})); }));
@ -273,7 +338,7 @@ define([
if (parsed.type === "file") { if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel) // `isNewChannel` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata // `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(window.location.href, password, w(function (e, size) { Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) {
if (size !== 0) { return void todo(); } if (size !== 0) { return void todo(); }
// Wrong password or deleted file? // Wrong password or deleted file?
askPassword(true, passwordCfg); askPassword(true, passwordCfg);
@ -281,7 +346,7 @@ define([
return; return;
} }
// Not a file, so we can use `isNewChannel` // Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(window.location.href, password, w(function(e, isNew) { Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) {
if (!isNew) { return void todo(); } if (!isNew) { return void todo(); }
if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) { if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) {
// Error, wrong password stored, the view seed has changed with the password // Error, wrong password stored, the view seed has changed with the password
@ -305,10 +370,12 @@ define([
} }
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// Check if the pad exists on server // Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; } if (!currentPad.hash) { isNewFile = true; return; }
if (realtime) { if (realtime) {
Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) { // TODO we probably don't need to check again for password-protected pads
// (we use isNewChannel to test the password...)
Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) {
if (e) { return console.error(e); } if (e) { return console.error(e); }
isNewFile = Boolean(isNew); isNewFile = Boolean(isNew);
})); }));
@ -322,11 +389,12 @@ define([
readOnly = false; readOnly = false;
} }
Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
var parsed = Utils.Hash.parsePadUrl(window.location.href); var parsed = Utils.Hash.parsePadUrl(currentPad.href);
var burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey; var burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey;
if (!parsed.type) { throw new Error(); } if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.UserObject.getDefaultName(parsed); var defaultTitle = Utils.UserObject.getDefaultName(parsed);
var edPublic, curvePublic, notifications, isTemplate; var edPublic, curvePublic, notifications, isTemplate;
var settings = {};
var forceCreationScreen = cfg.useCreationScreen && var forceCreationScreen = cfg.useCreationScreen &&
sessionStorage[Utils.Constants.displayPadCreationScreen]; sessionStorage[Utils.Constants.displayPadCreationScreen];
delete sessionStorage[Utils.Constants.displayPadCreationScreen]; delete sessionStorage[Utils.Constants.displayPadCreationScreen];
@ -340,9 +408,10 @@ define([
edPublic = metaObj.priv.edPublic; // needed to create an owned pad edPublic = metaObj.priv.edPublic; // needed to create an owned pad
curvePublic = metaObj.user.curvePublic; curvePublic = metaObj.user.curvePublic;
notifications = metaObj.user.notifications; notifications = metaObj.user.notifications;
settings = metaObj.priv.settings;
})); }));
if (typeof(isTemplate) === "undefined") { if (typeof(isTemplate) === "undefined") {
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) { Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) {
if (err) { console.log(err); } if (err) { console.log(err); }
isTemplate = t; isTemplate = t;
})); }));
@ -368,7 +437,7 @@ define([
upgradeURL: Cryptpad.upgradeURL upgradeURL: Cryptpad.upgradeURL
}, },
isNewFile: isNewFile, isNewFile: isNewFile,
isDeleted: isNewFile && window.location.hash.length > 0, isDeleted: isNewFile && currentPad.hash.length > 0,
forceCreationScreen: forceCreationScreen, forceCreationScreen: forceCreationScreen,
password: password, password: password,
channel: secret.channel, channel: secret.channel,
@ -393,7 +462,7 @@ define([
additionalPriv.registeredOnly = true; additionalPriv.registeredOnly = true;
} }
if (['debug', 'profile'].indexOf(parsed.type) !== -1) { if (['debug', 'profile'].indexOf(currentPad.app) !== -1) {
additionalPriv.hashes = hashes; additionalPriv.hashes = hashes;
} }
@ -487,7 +556,7 @@ define([
}); });
sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) { sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) {
sessionStorage.redirectTo = window.location.href; sessionStorage.redirectTo = currentPad.href;
cb(); cb();
}); });
@ -570,7 +639,18 @@ define([
channel: secret.channel, channel: secret.channel,
path: initialPathInDrive // Where to store the pad if we don't have it in our drive path: initialPathInDrive // Where to store the pad if we don't have it in our drive
}; };
Cryptpad.setPadTitle(data, function (err) { Cryptpad.setPadTitle(data, function (err, obj) {
if (!err && !(obj && obj.notStored)) {
// No error and the pad was correctly stored
// hide the hash
var opts = parsed.getOptions();
var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']);
if (!useUnsafe && window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
window.history.replaceState({}, window.document.title, hash);
}
}
cb({error: err}); cb({error: err});
}); });
}); });
@ -580,6 +660,9 @@ define([
}); });
sframeChan.on('EV_SET_HASH', function (hash) { sframeChan.on('EV_SET_HASH', function (hash) {
// In this case, we want to set the hash for the next page reload
// This hash is a category for the sidebar layout apps
// No need to store it in memory
window.location.hash = hash; window.location.hash = hash;
}); });
@ -595,6 +678,17 @@ define([
forceSave: true forceSave: true
}; };
Cryptpad.setPadTitle(data, function (err) { Cryptpad.setPadTitle(data, function (err) {
if (!err && !(obj && obj.notStored)) {
// No error and the pad was correctly stored
// hide the hash
var opts = parsed.getOptions();
var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']);
if (!useUnsafe && window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
window.history.replaceState({}, window.document.title, hash);
}
}
cb({error: err}); cb({error: err});
}); });
}); });
@ -801,15 +895,19 @@ define([
// Present mode URL // Present mode URL
sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) { sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {
var parsed = Utils.Hash.parsePadUrl(window.location.href); var parsed = Utils.Hash.parsePadUrl(currentPad.href);
cb(parsed.hashData && parsed.hashData.present); cb(parsed.hashData && parsed.hashData.present);
}); });
sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) { sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) {
var parsed = Utils.Hash.parsePadUrl(window.location.href); // Update the rendered hash and the full hash with the "present" settings
window.location.href = parsed.getUrl({ var opts = parsed.getOptions();
embed: parsed.hashData.embed, opts.present = data;
present: data // Full hash
}); currentPad.href = parsed.getUrl(opts);
if (parsed.hashData) { currentPad.hash = parsed.hashData.getHash(opts); }
// Rendered (maybe hidden) hash
var hiddenParsed = Utils.Hash.parsePadUrl(window.location.href);
window.location.href = hiddenParsed.getUrl(opts);
}); });
@ -1011,7 +1109,7 @@ define([
}); });
sframeChan.on('Q_BLOB_PASSWORD_CHANGE', function (data, cb) { sframeChan.on('Q_BLOB_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || window.location.href; data.href = data.href || currentPad.href;
var onPending = function (cb) { var onPending = function (cb) {
sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) { sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) {
if (obj && obj.cancel) { cb(); } if (obj && obj.cancel) { cb(); }
@ -1027,12 +1125,12 @@ define([
}); });
sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) { sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || window.location.href; data.href = data.href || currentPad.href;
Cryptpad.changeOOPassword(data, cb); Cryptpad.changeOOPassword(data, cb);
}); });
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) { sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || window.location.href; data.href = data.href || currentPad.href;
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb); Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
}); });
@ -1227,14 +1325,26 @@ define([
} }
} catch (e) {} } catch (e) {}
// If our channel was deleted from all of our drives, sitch back to full hash
// in the address bar
Cryptpad.padRpc.onChannelDeleted.reg(function (channel) {
if (channel !== secret.channel) { return; }
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.href = currentPad.href;
window.onhashchange = ohc;
ohc({reset: true});
});
// Join the netflux channel // Join the netflux channel
var rtStarted = false; var rtStarted = false;
var startRealtime = function (rtConfig) { var startRealtime = function (rtConfig) {
rtConfig = rtConfig || {}; rtConfig = rtConfig || {};
rtStarted = true; rtStarted = true;
var replaceHash = function (hash) { var replaceHash = function (hash) {
// The pad has just been created but is not stored yet. We'll switch
// to hidden hash once the pad is stored
if (window.history && window.history.replaceState) { if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; } if (!/^#/.test(hash)) { hash = '#' + hash; }
window.history.replaceState({}, window.document.title, hash); window.history.replaceState({}, window.document.title, hash);
@ -1250,7 +1360,7 @@ define([
Cryptpad.padRpc.onReadyEvent.reg(function () { Cryptpad.padRpc.onReadyEvent.reg(function () {
Cryptpad.burnPad({ Cryptpad.burnPad({
password: password, password: password,
href: window.location.href, href: currentPad.href,
channel: secret.channel, channel: secret.channel,
ownerKey: burnAfterReading ownerKey: burnAfterReading
}); });
@ -1265,7 +1375,7 @@ define([
readOnly: readOnly, readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys), crypto: Crypto.createEncryptor(secret.keys),
onConnect: function () { onConnect: function () {
if (window.location.hash && window.location.hash !== '#') { if (currentPad.hash && currentPad.hash !== '#') {
/*window.location = parsed.getUrl({ /*window.location = parsed.getUrl({
present: parsed.hashData.present, present: parsed.hashData.present,
embed: parsed.hashData.embed embed: parsed.hashData.embed
@ -1278,11 +1388,11 @@ define([
}; };
nThen(function (waitFor) { nThen(function (waitFor) {
if (isNewFile && cfg.owned && !window.location.hash) { if (isNewFile && cfg.owned && !currentPad.hash) {
Cryptpad.getMetadata(waitFor(function (err, m) { Cryptpad.getMetadata(waitFor(function (err, m) {
cpNfCfg.owners = [m.priv.edPublic]; cpNfCfg.owners = [m.priv.edPublic];
})); }));
} else if (isNewFile && !cfg.useCreationScreen && window.location.hash) { } else if (isNewFile && !cfg.useCreationScreen && currentPad.hash) {
console.log("new file with hash in the address bar in an app without pcs and which requires owners"); console.log("new file with hash in the address bar in an app without pcs and which requires owners");
sframeChan.onReady(function () { sframeChan.onReady(function () {
sframeChan.query("EV_LOADING_ERROR", "DELETED"); sframeChan.query("EV_LOADING_ERROR", "DELETED");
@ -1309,11 +1419,13 @@ define([
var ohc = window.onhashchange; var ohc = window.onhashchange;
window.onhashchange = function () {}; window.onhashchange = function () {};
window.location.hash = newHash; window.location.hash = newHash;
currentPad.hash = newHash;
currentPad.href = '/' + parsed.type + '/#' + newHash;
window.onhashchange = ohc; window.onhashchange = ohc;
ohc({reset: true}); ohc({reset: true});
// Update metadata values and send new metadata inside // Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href); parsed = Utils.Hash.parsePadUrl(currentPad.href);
defaultTitle = Utils.UserObject.getDefaultName(parsed); defaultTitle = Utils.UserObject.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret); hashes = Utils.Hash.getHashes(secret);
readOnly = false; readOnly = false;

@ -603,6 +603,10 @@ define([
UI.addTooltips(); UI.addTooltips();
ctx.sframeChan.on("EV_PAD_NODATA", function () {
UI.errorLoadingScreen(Messages.safeLinks_error);
});
ctx.sframeChan.on("EV_PAD_PASSWORD", function (cfg) { ctx.sframeChan.on("EV_PAD_PASSWORD", function (cfg) {
UIElements.displayPasswordPrompt(funcs, cfg); UIElements.displayPasswordPrompt(funcs, cfg);
}); });

@ -12,7 +12,7 @@
"media": "Multimèdia", "media": "Multimèdia",
"todo": "Tasques", "todo": "Tasques",
"contacts": "Contactes", "contacts": "Contactes",
"sheet": "Full (Beta)", "sheet": "Full de càlcul",
"teams": "Equips" "teams": "Equips"
}, },
"button_newpad": "Nou document", "button_newpad": "Nou document",
@ -34,7 +34,7 @@
"inactiveError": "Donada la seva inactivitat, aquest document s'ha esborrat. Premeu Esc per crear un nou document.", "inactiveError": "Donada la seva inactivitat, aquest document s'ha esborrat. Premeu Esc per crear un nou document.",
"chainpadError": "Hi ha hagut un error crític mentre s'actualitzava el vostre contingut. Aquesta pàgina es manté en mode només de lectura per assegurar que no perdreu el que ja heu fet.<br>Premeu <em>Esc</em> per continuar veient aquest document o torneu a carregar la pàgina per provar de continuar editant-lo.", "chainpadError": "Hi ha hagut un error crític mentre s'actualitzava el vostre contingut. Aquesta pàgina es manté en mode només de lectura per assegurar que no perdreu el que ja heu fet.<br>Premeu <em>Esc</em> per continuar veient aquest document o torneu a carregar la pàgina per provar de continuar editant-lo.",
"invalidHashError": "El document que heu demanat té una adreça URL no vàlida.", "invalidHashError": "El document que heu demanat té una adreça URL no vàlida.",
"errorCopy": " Encara podeu copiar el contingut en una altra ubicació prement <em>Esc</em>.<br>Un cop deixeu aquesta pàgina, desapareixerà per sempre!", "errorCopy": " Encara podeu accedir al contingut prement <em>Esc</em>.<br>Un cop tanqueu aquesta finestra no hi podreu tornar a accedir.",
"errorRedirectToHome": "Premeu <em>Esc</em> per tornar al vostre CryptDrive.", "errorRedirectToHome": "Premeu <em>Esc</em> per tornar al vostre CryptDrive.",
"newVersionError": "Hi ha una nova versió disponible de CryptPad.<br><a href='#'>Torneu a carregar</a> la pàgina per utilitzar la versió nova o premeu Esc per accedir al vostre contingut en mode <b>fora de línia</b>.", "newVersionError": "Hi ha una nova versió disponible de CryptPad.<br><a href='#'>Torneu a carregar</a> la pàgina per utilitzar la versió nova o premeu Esc per accedir al vostre contingut en mode <b>fora de línia</b>.",
"loading": "Carregant...", "loading": "Carregant...",
@ -531,5 +531,59 @@
"settings_padSpellcheckTitle": "Correcció ortogràfica", "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_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_padSpellcheckLabel": "Activa la correcció ortogràfica",
"settings_creationSkip": "Salta la pantalla de creació de document" "settings_creationSkip": "Salta la pantalla de creació de document",
"settings_creationSkipHint": "La pantalla de creació de documents ofereix noves opcions, donant-vos més control sobre les vostres dades. Tot i això, pot alentir una mica la feina afegint un pas addicional i, per això, teniu l'opció de saltar aquesta pantalla i utilitzar les opcions per defecte que hi ha seleccionades.",
"settings_creationSkipTrue": "Salta",
"settings_creationSkipFalse": "Mostra",
"settings_templateSkip": "Salta la finestra de selecció de plantilla",
"settings_templateSkipHint": "Quan genereu un document nou buit, si teniu desades plantilles per aquest tipus de document, apareix una finestra preguntant-vos si voleu utilitzar una plantilla. Aquí podeu triar si no voleu veure mai més la finestra i no utilitzar una plantilla.",
"settings_ownDriveTitle": "Habilita les darreres funcionalitats del compte",
"settings_ownDriveHint": "Per raons tècniques, els comptes antics no tenen accés a totes les funcionalitats noves. Si feu una actualització a un compte nou, preparareu el vostre CryptDrive per les properes funcionalitats sense interrompre la vostra activitat habitual.",
"settings_ownDriveButton": "Milloreu el vostre compte",
"settings_ownDriveConfirm": "Millorar el vostre compte porta una estona. Necessitareu tornar-vos a connectar en tots els vostres dispositius. Segur que ho voleu fer?",
"settings_ownDrivePending": "El vostre compte s'està posant al dia. No tanqueu ni torneu a carregar aquesta pàgina fins que el procés hagi acabat.",
"settings_changePasswordTitle": "Canvieu la contrasenya",
"settings_changePasswordHint": "Canvieu la contrasenya del vostre compte. Introduïu la contrasenya actual i confirmeu la nova escrivint-la dos cops.<br><b>Si l'oblideu, no podem recuperar la vostra contrasenya, aneu amb molt de compte!</b>",
"settings_changePasswordButton": "Canvia la contrasenya",
"settings_changePasswordCurrent": "Contrasenya actual",
"settings_changePasswordNew": "Nova contrasenya",
"settings_changePasswordNewConfirm": "Confirma la nova contrasenya",
"settings_changePasswordConfirm": "Segur que voleu canviar la contrasenya? Necessitareu tornar-vos a connectar en tots els dispositius.",
"settings_changePasswordError": "Hi ha hagut una errada inesperada. Si no podeu iniciar la sessió o canviar la contrasenya, contacteu l'administració de CryptPad.",
"settings_changePasswordPending": "S'està actualitzant la contrasenya. Si us plau, no tanqueu ni carregueu de nou la pàgina fins que el procés s'hagi acabat.",
"settings_changePasswordNewPasswordSameAsOld": "La contrasenya nova cal que sigui diferent de l'actual.",
"settings_cursorColorTitle": "Color del cursor",
"settings_cursorColorHint": "Canvieu el color associat al vostre compte en els documents col·laboratius.",
"settings_cursorShareTitle": "Comparteix la posició del meu cursor",
"settings_cursorShareHint": "Podeu decidir si, als documents col·laboratius, voleu que la resta de persones vegin el vostre cursor.",
"settings_cursorShareLabel": "Comparteix la posició",
"settings_cursorShowTitle": "Mostra la posició del cursor de la resta",
"settings_cursorShowHint": "Podeu triar si, als documents col·laboratius, voleu veure el cursor de les altres persones.",
"settings_cursorShowLabel": "Mostra els cursors",
"upload_title": "Carrega fitxer",
"upload_type": "Tipus",
"upload_modal_title": "Opcions per carregar fitxers",
"upload_modal_filename": "Nom del fitxer (extensió <em>{0}</em> afegit automàticament)",
"upload_modal_owner": "Fitxer propi",
"uploadFolder_modal_title": "Opcions per carregar carpetes",
"uploadFolder_modal_filesPassword": "Fitxers de contrasenya",
"uploadFolder_modal_owner": "Fitxers propis",
"uploadFolder_modal_forceSave": "Deseu fitxers al vostre CryptDrive",
"upload_serverError": "Errada interna: ara mateix és impossible carregar el fitxer.",
"upload_uploadPending": "Ja teniu una càrrega en marxa. Voleu cancel·lar-la i carregar aquest altre fitxer?",
"upload_success": "El fitxer ({0}) ha estat carregat correctament i afegit al vostre CryptDrive.",
"upload_notEnoughSpace": "No hi ha prou espai al CryptDrive per aquest fitxer.",
"upload_notEnoughSpaceBrief": "No hi ha prou espai",
"upload_tooLarge": "Aquest fitxer supera la mida màxima permesa.",
"upload_tooLargeBrief": "El fitxer és massa gran",
"upload_choose": "Trieu un fitxer",
"upload_pending": "Pendent",
"upload_cancelled": "Cancel·lat",
"upload_name": "Nom del fitxer",
"upload_size": "Mida",
"upload_progress": "Procés",
"upload_mustLogin": "Cal que inicieu la sessió per carregar un fitxer",
"upload_up": "Carrega",
"download_button": "Desxifra i descarrega",
"download_mt_button": "Descarrega"
} }

@ -12,7 +12,7 @@
"media": "Medien", "media": "Medien",
"todo": "Aufgaben", "todo": "Aufgaben",
"contacts": "Kontakte", "contacts": "Kontakte",
"sheet": "Tabelle (Beta)", "sheet": "Tabelle",
"teams": "Teams" "teams": "Teams"
}, },
"button_newpad": "Neues Rich-Text-Pad", "button_newpad": "Neues Rich-Text-Pad",
@ -92,7 +92,7 @@
"exportButtonTitle": "Exportiere dieses Pad in eine lokale Datei", "exportButtonTitle": "Exportiere dieses Pad in eine lokale Datei",
"exportPrompt": "Wie möchtest du die Datei nennen?", "exportPrompt": "Wie möchtest du die Datei nennen?",
"changeNamePrompt": "Ändere deinen Namen (oder lasse dieses Feld leer, um anonym zu bleiben): ", "changeNamePrompt": "Ändere deinen Namen (oder lasse dieses Feld leer, um anonym zu bleiben): ",
"user_rename": "Bearbeite deinen Anzeigename", "user_rename": "Anzeigename ändern",
"user_displayName": "Anzeigename", "user_displayName": "Anzeigename",
"user_accountName": "Kontoname", "user_accountName": "Kontoname",
"clickToEdit": "Zum Bearbeiten klicken", "clickToEdit": "Zum Bearbeiten klicken",
@ -275,11 +275,11 @@
"profile_description": "Beschreibung", "profile_description": "Beschreibung",
"profile_fieldSaved": "Neuer Wert gespeichert: {0}", "profile_fieldSaved": "Neuer Wert gespeichert: {0}",
"profile_viewMyProfile": "Mein Profil anzeigen", "profile_viewMyProfile": "Mein Profil anzeigen",
"userlist_addAsFriendTitle": "Benutzer \"{0}\" eine Freundschaftsanfrage senden", "userlist_addAsFriendTitle": "Benutzer \"{0}\" eine Kontaktanfrage senden",
"contacts_title": "Kontakte", "contacts_title": "Kontakte",
"contacts_addError": "Fehler bei dem Hinzufügen des Kontakts zur Liste", "contacts_addError": "Fehler bei dem Hinzufügen des Kontakts zur Liste",
"contacts_added": "Verbindungseinladung angenommen.", "contacts_added": "Kontaktanfrage akzeptiert.",
"contacts_rejected": "Verbindungseinladung abgelehnt", "contacts_rejected": "Kontaktanfrage abgelehnt",
"contacts_request": "Benutzer <em>{0}</em> möchte dich als Kontakt hinzufügen. <b>Annehmen<b>?", "contacts_request": "Benutzer <em>{0}</em> möchte dich als Kontakt hinzufügen. <b>Annehmen<b>?",
"contacts_send": "Senden", "contacts_send": "Senden",
"contacts_remove": "Diesen Kontakt entfernen", "contacts_remove": "Diesen Kontakt entfernen",
@ -664,7 +664,7 @@
"features_f_social": "Soziale Anwendungen", "features_f_social": "Soziale Anwendungen",
"features_f_social_note": "Ein Profil erstellen, ein Profilbild verwenden, mit Kontakten chatten", "features_f_social_note": "Ein Profil erstellen, ein Profilbild verwenden, mit Kontakten chatten",
"features_f_file1": "Dateien hochladen und teilen", "features_f_file1": "Dateien hochladen und teilen",
"features_f_file1_note": "Dateien mit Freunden teilen oder sie in Dokumenten einbetten", "features_f_file1_note": "Dateien mit Kontakten teilen oder sie in Dokumenten einbetten",
"features_f_storage1": "Langfristige Speicherung (50 MB)", "features_f_storage1": "Langfristige Speicherung (50 MB)",
"features_f_storage1_note": "Dateien in deinem CryptDrive werden nicht wegen Inaktivität gelöscht", "features_f_storage1_note": "Dateien in deinem CryptDrive werden nicht wegen Inaktivität gelöscht",
"features_f_register": "Registrieren (kostenlos)", "features_f_register": "Registrieren (kostenlos)",
@ -767,7 +767,7 @@
"a": "Registrierte Benutzer können Funktionen verwenden, die anonyme Nutzer nicht verwenden können. Es gibt <a href='/features.html' target='_blank'>hier</a> eine entsprechende Übersicht." "a": "Registrierte Benutzer können Funktionen verwenden, die anonyme Nutzer nicht verwenden können. Es gibt <a href='/features.html' target='_blank'>hier</a> eine entsprechende Übersicht."
}, },
"share": { "share": {
"q": "Wie kann ich den Zugang zu einem verschlüsselten Pad mit Freunden teilen?", "q": "Wie kann ich den Zugang zu einem verschlüsselten Pad mit Kontakten teilen?",
"a": "CryptPad fügt den geheimen Schlüssel deines Pad nach dem Zeichen <em>#</em> zur URL hinzu. Alles, was nach diesem Zeichen kommt, wird nicht zum Server gesendet. Also haben wir nie Zugang zu deinen Schlüsseln. Wenn du den Link zu einem Pad teilst, teilst du auch die Fähigkeit zum Lesen und zum Bearbeiten." "a": "CryptPad fügt den geheimen Schlüssel deines Pad nach dem Zeichen <em>#</em> zur URL hinzu. Alles, was nach diesem Zeichen kommt, wird nicht zum Server gesendet. Also haben wir nie Zugang zu deinen Schlüsseln. Wenn du den Link zu einem Pad teilst, teilst du auch die Fähigkeit zum Lesen und zum Bearbeiten."
}, },
"remove": { "remove": {
@ -799,7 +799,7 @@
"title": "Andere Fragen", "title": "Andere Fragen",
"pay": { "pay": {
"q": "Wieso soll ich zahlen, wenn so viele Funktionen sowieso kostenfrei sind?", "q": "Wieso soll ich zahlen, wenn so viele Funktionen sowieso kostenfrei sind?",
"a": "Wir geben Unterstützern zusätzlichen Speicherplatz sowie die Möglichkeit, die Speicherplatzbegrenzung ihrer Freunde zu erhöhen (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>erfahre mehr</a>).<br><br> Über diese diese kurzfristigen Vorteile hinaus kannst du, wenn du ein Premiumangebot annimmst, die aktive Weiterentwicklung von CryptPad fördern. Das beinhaltet, Fehler zu beseitigen, neue Funktionen zu umzusetzen und Installationen von CryptPad auf eigenen Servern zu erleichtern. Zusätzlich hilfst du, anderen Anbietern zu beweisen, dass Leute datenschutzfreundliche Technologien unterstützen. Wir hoffen, dass Geschäftsmodelle, die auf dem Verkauf von Benutzerdaten basieren, letztendlich der Vergangenheit angehören werden.<br><br>Außerdem glauben wir, dass es gut ist, die Funktionen von CryptPad kostenfrei anzubieten. Denn jeder verdient persönlichen Datenschutz und nicht nur Personen mit hohem Einkommen. Durch deine Unterstützung hilfst du uns, zu ermöglichen, dass auch Menschen mit geringerem Einkommen diese grundlegenden Funktionen genießen können, ohne dass ein Preisetikett daran klebt." "a": "Wir geben Unterstützern zusätzlichen Speicherplatz sowie die Möglichkeit, die Speicherplatzbegrenzung ihrer Kontakte zu erhöhen (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>erfahre mehr</a>).<br><br>Über diese diese kurzfristigen Vorteile hinaus kannst du, wenn du ein Premiumangebot annimmst, die aktive Weiterentwicklung von CryptPad fördern. Das beinhaltet, Fehler zu beseitigen, neue Funktionen zu umzusetzen und Installationen von CryptPad auf eigenen Servern zu erleichtern. Zusätzlich hilfst du, anderen Anbietern zu beweisen, dass Leute datenschutzfreundliche Technologien unterstützen. Wir hoffen, dass Geschäftsmodelle, die auf dem Verkauf von Benutzerdaten basieren, letztendlich der Vergangenheit angehören werden.<br><br>Außerdem glauben wir, dass es gut ist, die Funktionen von CryptPad kostenfrei anzubieten. Denn jeder verdient persönlichen Datenschutz und nicht nur Personen mit hohem Einkommen. Durch deine Unterstützung hilfst du uns, zu ermöglichen, dass auch Menschen mit geringerem Einkommen diese grundlegenden Funktionen genießen können, ohne dass ein Preisetikett daran klebt."
}, },
"goal": { "goal": {
"q": "Was ist euer Ziel?", "q": "Was ist euer Ziel?",
@ -860,7 +860,7 @@
"colors": "Ändere Text- und Hintergrundfarbe mit den Schaltflächen <span class=\"fa fa-i-cursor\"></span> und <span class=\"fa fa-square\"></span>" "colors": "Ändere Text- und Hintergrundfarbe mit den Schaltflächen <span class=\"fa fa-i-cursor\"></span> und <span class=\"fa fa-square\"></span>"
}, },
"poll": { "poll": {
"decisions": "Treffe Entscheidungen gemeinsam mit deinen Bekannten", "decisions": "Treffe Entscheidungen gemeinsam mit deinen Kontakten",
"options": "Mache Vorschläge und teile deine Präferenzen mit", "options": "Mache Vorschläge und teile deine Präferenzen mit",
"choices": "Klicke in die Zellen in deiner Spalte, um zwischen ja (<strong>✔</strong>), viellecht (<strong>~</strong>), oder nein (<strong>✖</strong>) zu wählen", "choices": "Klicke in die Zellen in deiner Spalte, um zwischen ja (<strong>✔</strong>), viellecht (<strong>~</strong>), oder nein (<strong>✖</strong>) zu wählen",
"submit": "Klicke auf <strong>Senden</strong>, damit deine Auswahl für andere sichtbar wird" "submit": "Klicke auf <strong>Senden</strong>, damit deine Auswahl für andere sichtbar wird"
@ -878,7 +878,7 @@
}, },
"driveReadmeTitle": "Was ist CryptPad?", "driveReadmeTitle": "Was ist CryptPad?",
"readme_welcome": "Willkommen zu CryptPad!", "readme_welcome": "Willkommen zu CryptPad!",
"readme_p1": "Willkommen zu CryptPad, hier kannst du deine Notizen aufschreiben, allein oder mit Bekannten.", "readme_p1": "Willkommen zu CryptPad, hier kannst du deine Notizen aufschreiben, allein oder mit Kontakten.",
"readme_p2": "Dieses Dokument gibt dir einen kurzen Überblick, wie du CryptPad verwenden kannst, um Notizen zu schreiben, sie zu organisieren und mit anderen zusammen zu arbeiten.", "readme_p2": "Dieses Dokument gibt dir einen kurzen Überblick, wie du CryptPad verwenden kannst, um Notizen zu schreiben, sie zu organisieren und mit anderen zusammen zu arbeiten.",
"readme_cat1": "Lerne dein CryptDrive kennen", "readme_cat1": "Lerne dein CryptDrive kennen",
"readme_cat1_l1": "Ein Pad erstellen: Klicke in deinem CryptDrive auf {0} und dann auf {1}.", "readme_cat1_l1": "Ein Pad erstellen: Klicke in deinem CryptDrive auf {0} und dann auf {1}.",
@ -1053,18 +1053,17 @@
"friendRequest_later": "Später entscheiden", "friendRequest_later": "Später entscheiden",
"friendRequest_accept": "Akzeptieren (Enter)", "friendRequest_accept": "Akzeptieren (Enter)",
"friendRequest_decline": "Ablehnen", "friendRequest_decline": "Ablehnen",
"friendRequest_declined": "<b>{0}</b> hat deine Freundschaftsanfrage abgelehnt", "friendRequest_declined": "<b>{0}</b> hat deine Kontaktanfrage abgelehnt",
"friendRequest_accepted": "<b>{0}</b> hat deine Freundschaftsanfrage akzeptiert", "friendRequest_accepted": "<b>{0}</b> hat deine Kontaktanfrage akzeptiert",
"friendRequest_received": "<b>{0}</b> möchte mit dir befreundet sein", "friendRequest_received": "<b>{0}</b> möchte dein Kontakt sein",
"friendRequest_notification": "<b>{0}</b> hat dir eine Freundschaftsanfrage geschickt", "friendRequest_notification": "<b>{0}</b> hat dir eine Kontaktanfrage geschickt",
"notifications_empty": "Keine Benachrichtigungen verfügbar", "notifications_empty": "Keine Benachrichtigungen verfügbar",
"notifications_title": "Du hast ungelesene Benachrichtigungen", "notifications_title": "Du hast ungelesene Benachrichtigungen",
"profile_addDescription": "Beschreibung hinzufügen", "profile_addDescription": "Beschreibung hinzufügen",
"profile_editDescription": "Deine Beschreibung bearbeiten", "profile_editDescription": "Deine Beschreibung bearbeiten",
"profile_addLink": "Link zu deiner Website hinzufügen", "profile_addLink": "Link zu deiner Website hinzufügen",
"profile_info": "Andere Nutzer können dein Profil finden, indem sie auf deinen Avatar in der Benutzerliste eines Dokumentes klicken.", "profile_info": "Andere Nutzer können dein Profil finden, indem sie auf deinen Avatar in der Benutzerliste eines Dokumentes klicken.",
"profile_friendRequestSent": "Freundschaftsanfrage gesendet...", "profile_friendRequestSent": "Kontaktanfrage gesendet...",
"profile_friend": "{0} ist mit dir befreundet",
"notification_padShared": "{0} hat ein Pad mit dir geteilt: <b>{1}</b>", "notification_padShared": "{0} hat ein Pad mit dir geteilt: <b>{1}</b>",
"notification_fileShared": "{0} hat eine Datei mit dir geteilt: <b>{1}</b>", "notification_fileShared": "{0} hat eine Datei mit dir geteilt: <b>{1}</b>",
"notification_folderShared": "{0} hat einen Ordner mit dir geteilt: <b>{1}</b>", "notification_folderShared": "{0} hat einen Ordner mit dir geteilt: <b>{1}</b>",
@ -1075,7 +1074,7 @@
"share_deselectAll": "Alle abwählen", "share_deselectAll": "Alle abwählen",
"notifications_dismiss": "Verbergen", "notifications_dismiss": "Verbergen",
"fm_info_sharedFolderHistory": "Dies ist nur der Verlauf deines geteilten Ordners: <b>{0}</b><br/>Dein CryptDrive bleibt beim Navigieren im Nur-Lesen-Modus.", "fm_info_sharedFolderHistory": "Dies ist nur der Verlauf deines geteilten Ordners: <b>{0}</b><br/>Dein CryptDrive bleibt beim Navigieren im Nur-Lesen-Modus.",
"share_description": "Wähle aus, was du teilen möchtest. Dir wird dann ein entsprechender Link anzeigt. Du kannst es auch direkt an deine Freunde in CryptPad senden.", "share_description": "Wähle aus, was du teilen möchtest. Dir wird dann ein entsprechender Link anzeigt. Du kannst es auch direkt an deine Kontakte in CryptPad senden.",
"fc_expandAll": "Alle ausklappen", "fc_expandAll": "Alle ausklappen",
"fc_collapseAll": "Alle einklappen", "fc_collapseAll": "Alle einklappen",
"fc_color": "Farbe ändern", "fc_color": "Farbe ändern",
@ -1100,7 +1099,7 @@
"support_formMessage": "Gib deine Nachricht ein...", "support_formMessage": "Gib deine Nachricht ein...",
"support_cat_tickets": "Vorhandene Tickets", "support_cat_tickets": "Vorhandene Tickets",
"support_listTitle": "Support-Tickets", "support_listTitle": "Support-Tickets",
"support_listHint": "Hier ist die Liste der an die Administratoren gesendeten Tickets und der dazugehörigen Antworten. Ein geschlossenes Ticket kann nicht wieder geöffnet werden, du musst ein Ticket eröffnen. Du kannst geschlossene Tickets ausblenden, aber sie werden weiterhin für die Administratoren sichtbar sein.", "support_listHint": "Hier ist die Liste der an die Administratoren gesendeten Tickets und der dazugehörigen Antworten. Ein geschlossenes Ticket kann nicht wieder geöffnet werden, du kannst jedoch ein neues Ticket eröffnen. Du kannst geschlossene Tickets ausblenden, aber sie werden weiterhin für die Administratoren sichtbar sein.",
"support_answer": "Antworten", "support_answer": "Antworten",
"support_close": "Ticket schließen", "support_close": "Ticket schließen",
"support_remove": "Ticket entfernen", "support_remove": "Ticket entfernen",
@ -1111,7 +1110,7 @@
"notificationsPage": "Benachrichtigungen", "notificationsPage": "Benachrichtigungen",
"openNotificationsApp": "Benachrichtigungspanel öffnen", "openNotificationsApp": "Benachrichtigungspanel öffnen",
"notifications_cat_all": "Alle", "notifications_cat_all": "Alle",
"notifications_cat_friends": "Freundschaftsanfragen", "notifications_cat_friends": "Kontaktanfragen",
"notifications_cat_pads": "Mit mir geteilt", "notifications_cat_pads": "Mit mir geteilt",
"notifications_cat_archived": "Verlauf", "notifications_cat_archived": "Verlauf",
"notifications_dismissAll": "Alle verbergen", "notifications_dismissAll": "Alle verbergen",
@ -1119,8 +1118,6 @@
"requestEdit_button": "Bearbeitungsrechte anfragen", "requestEdit_button": "Bearbeitungsrechte anfragen",
"requestEdit_dialog": "Bist du sicher, dass du den Eigentümer um Bearbeitungsrechte für das Pad bitten möchtest?", "requestEdit_dialog": "Bist du sicher, dass du den Eigentümer um Bearbeitungsrechte für das Pad bitten möchtest?",
"requestEdit_confirm": "{1} hat Bearbeitungsrechte für das Pad <b>{0}</b> angefragt. Möchtest du die Rechte vergeben?", "requestEdit_confirm": "{1} hat Bearbeitungsrechte für das Pad <b>{0}</b> angefragt. Möchtest du die Rechte vergeben?",
"requestEdit_fromFriend": "Du bist mit {0} befreundet",
"requestEdit_fromStranger": "Du bist <b>nicht</b> mit {0} befreundet",
"requestEdit_viewPad": "Pad in neuem Tab öffnen", "requestEdit_viewPad": "Pad in neuem Tab öffnen",
"later": "Später entscheiden", "later": "Später entscheiden",
"requestEdit_request": "{1} möchte das Pad <b>{0}</b> bearbeiten", "requestEdit_request": "{1} möchte das Pad <b>{0}</b> bearbeiten",
@ -1155,7 +1152,7 @@
"owner_add": "{0} möchte ein Eigentümer des Pads <b>{1}</b> sein. Bist du damit einverstanden?", "owner_add": "{0} möchte ein Eigentümer des Pads <b>{1}</b> sein. Bist du damit einverstanden?",
"owner_removeText": "Einen Eigentümer entfernen", "owner_removeText": "Einen Eigentümer entfernen",
"owner_removePendingText": "Eine ausstehende Einladung zurückziehen", "owner_removePendingText": "Eine ausstehende Einladung zurückziehen",
"owner_addText": "Einen Freund zur Mit-Eigentümerschaft einladen", "owner_addText": "Einen Kontakt zur Mit-Eigentümerschaft einladen",
"owner_removePendingButton": "Ausgewählte Einladungen zurückziehen", "owner_removePendingButton": "Ausgewählte Einladungen zurückziehen",
"owner_addButton": "Zur Eigentümerschaft einladen", "owner_addButton": "Zur Eigentümerschaft einladen",
"owner_addConfirm": "Mit-Eigentümer können den Inhalt bearbeiten und dich als Eigentümer entfernen. Bist du sicher?", "owner_addConfirm": "Mit-Eigentümer können den Inhalt bearbeiten und dich als Eigentümer entfernen. Bist du sicher?",
@ -1166,8 +1163,7 @@
"owner_removedPending": "{0} hat die Einladung zur Eigentümerschaft von <b>{1}</b> zurückgezogen", "owner_removedPending": "{0} hat die Einladung zur Eigentümerschaft von <b>{1}</b> zurückgezogen",
"share_linkTeam": "Zu Team-Drive hinzufügen", "share_linkTeam": "Zu Team-Drive hinzufügen",
"team_inviteModalButton": "Einladen", "team_inviteModalButton": "Einladen",
"team_pickFriends": "Freunde auswählen, um sie in dieses Team einzuladen", "team_pickFriends": "Kontakte auswählen, um sie in dieses Team einzuladen",
"team_noFriend": "Du bist derzeit mit keinen Freunden auf CryptPad verbunden.",
"team_pcsSelectLabel": "Speichern in", "team_pcsSelectLabel": "Speichern in",
"team_pcsSelectHelp": "Die Erstellung eines eigenen Pads im Drive deines Teams gibt die Eigentümerschaft an das Team.", "team_pcsSelectHelp": "Die Erstellung eines eigenen Pads im Drive deines Teams gibt die Eigentümerschaft an das Team.",
"team_invitedToTeam": "{0} hat dich zum Team eingeladen: <b>{1}</b>", "team_invitedToTeam": "{0} hat dich zum Team eingeladen: <b>{1}</b>",
@ -1189,7 +1185,7 @@
"team_rosterPromote": "Befördern", "team_rosterPromote": "Befördern",
"team_rosterDemote": "Degradieren", "team_rosterDemote": "Degradieren",
"team_rosterKick": "Aus dem Team entfernen", "team_rosterKick": "Aus dem Team entfernen",
"team_inviteButton": "Freunde einladen", "team_inviteButton": "Kontakte einladen",
"team_leaveButton": "Dieses Team verlassen", "team_leaveButton": "Dieses Team verlassen",
"team_leaveConfirm": "Wenn du dieses Team verlässt, verlierst du den Zugriff auf das dazugehörige CryptDrive, den Chatverlauf und andere Inhalte. Bist du sicher?", "team_leaveConfirm": "Wenn du dieses Team verlässt, verlierst du den Zugriff auf das dazugehörige CryptDrive, den Chatverlauf und andere Inhalte. Bist du sicher?",
"team_owner": "Eigentümer", "team_owner": "Eigentümer",
@ -1294,5 +1290,8 @@
"oo_exportInProgress": "Export wird durchgeführt", "oo_exportInProgress": "Export wird durchgeführt",
"oo_sheetMigration_loading": "Deine Tabelle wird auf die neueste Version aktualisiert", "oo_sheetMigration_loading": "Deine Tabelle wird auf die neueste Version aktualisiert",
"oo_sheetMigration_complete": "Eine aktualisierte Version ist verfügbar. Klicke auf OK, um neu zu laden.", "oo_sheetMigration_complete": "Eine aktualisierte Version ist verfügbar. Klicke auf OK, um neu zu laden.",
"oo_sheetMigration_anonymousEditor": "Die Bearbeitung dieser Tabelle ist für anonyme Benutzer deaktiviert, bis sie von einem registrierten Benutzer auf die neueste Version aktualisiert wird." "oo_sheetMigration_anonymousEditor": "Die Bearbeitung dieser Tabelle ist für anonyme Benutzer deaktiviert, bis sie von einem registrierten Benutzer auf die neueste Version aktualisiert wird.",
"imprint": "Impressum",
"isContact": "{0} ist einer deiner Kontakte",
"isNotContact": "{0} ist <b>nicht</b> einer deiner Kontakte"
} }

@ -11,7 +11,7 @@
"media": "Media", "media": "Media",
"todo": "Tehtävälista", "todo": "Tehtävälista",
"contacts": "Yhteystiedot", "contacts": "Yhteystiedot",
"sheet": "Taulukko (Beta)", "sheet": "Taulukko",
"teams": "Teams" "teams": "Teams"
}, },
"button_newpad": "Uusi Teksti-padi", "button_newpad": "Uusi Teksti-padi",
@ -619,7 +619,7 @@
"about_intro": "CryptPadia kehittää Pariisissa, Ranskassa ja Iasissa, Romaniassa toimiva<a href=\"http://xwiki.com\">XWiki SAS</a>-pienyrityksen tutkimusryhmä. CryptPadin parissa työskentelee kolme ryhmän ydinjäsentä ja lisäksi joitakin avustajia XWiki SAS:n sisältä ja ulkopuolelta.", "about_intro": "CryptPadia kehittää Pariisissa, Ranskassa ja Iasissa, Romaniassa toimiva<a href=\"http://xwiki.com\">XWiki SAS</a>-pienyrityksen tutkimusryhmä. CryptPadin parissa työskentelee kolme ryhmän ydinjäsentä ja lisäksi joitakin avustajia XWiki SAS:n sisältä ja ulkopuolelta.",
"about_core": "Ydinkehittäjät", "about_core": "Ydinkehittäjät",
"about_contributors": "Tärkeät avustajat", "about_contributors": "Tärkeät avustajat",
"main_info": "<h2>Luottamuksellista yhteistyötä</h2>Jaetut dokumentit mahdollistavat ideoiden jakamisen samalla kun <strong>nollatietoperiaate</strong>-teknologia suojaa yksityisyytesi - <strong>jopa meiltä</strong>.", "main_info": "<h2>Luottamuksellista yhteistyötä</h2> Jaa ideoita yhdessä jaettujen dokumenttien avulla.<strong>Nollatieto</strong>-teknologia turvaa yksityisyytesi - <strong>jopa meiltä</strong>.",
"main_catch_phrase": "Pilvipalvelu nollatietoperiaatteella", "main_catch_phrase": "Pilvipalvelu nollatietoperiaatteella",
"main_footerText": "CryptPadin avulla voit nopeasti luoda kollaboratiivisia dokumentteja muistiinpanoja ja yhteistä ideointia varten.", "main_footerText": "CryptPadin avulla voit nopeasti luoda kollaboratiivisia dokumentteja muistiinpanoja ja yhteistä ideointia varten.",
"footer_applications": "Sovellukset", "footer_applications": "Sovellukset",
@ -665,8 +665,6 @@
"requestEdit_button": "Pyydä muokkausoikeutta", "requestEdit_button": "Pyydä muokkausoikeutta",
"requestEdit_dialog": "Haluatko varmasti pyytää padin omistajalta muokkausoikeutta?", "requestEdit_dialog": "Haluatko varmasti pyytää padin omistajalta muokkausoikeutta?",
"requestEdit_confirm": "{1} on pyytänyt oikeutta muokata padia <b>{0}</b>. Haluatko myöntää muokkausoikeuden?", "requestEdit_confirm": "{1} on pyytänyt oikeutta muokata padia <b>{0}</b>. Haluatko myöntää muokkausoikeuden?",
"requestEdit_fromFriend": "Olet kaveri käyttäjän {0} kanssa",
"requestEdit_fromStranger": "<b>Et ole</b> käyttäjän {0} kaveri",
"requestEdit_viewPad": "Avaa padi uudessa välilehdessä", "requestEdit_viewPad": "Avaa padi uudessa välilehdessä",
"later": "Päätä myöhemmin", "later": "Päätä myöhemmin",
"requestEdit_request": "{1} haluaa muokata padia <b>{0}</b>", "requestEdit_request": "{1} haluaa muokata padia <b>{0}</b>",
@ -695,7 +693,6 @@
"share_linkTeam": "Lisää tiimin CryptDriveen", "share_linkTeam": "Lisää tiimin CryptDriveen",
"team_pickFriends": "Valitse tiimiin kutsuttavat kaverit", "team_pickFriends": "Valitse tiimiin kutsuttavat kaverit",
"team_inviteModalButton": "Kutsu", "team_inviteModalButton": "Kutsu",
"team_noFriend": "Sinulla ei ole vielä kavereita CryptPadissa.",
"drive_sfPassword": "Jaettu kansiosi {0} ei ole enää saatavilla. Se on joko poistettu omistajansa toimesta tai sille on asetettu uusi salasana. Voit poistaa tämän kansion CryptDrivestasi tai palauttaa käyttöoikeuden käyttämällä uutta salasanaa.", "drive_sfPassword": "Jaettu kansiosi {0} ei ole enää saatavilla. Se on joko poistettu omistajansa toimesta tai sille on asetettu uusi salasana. Voit poistaa tämän kansion CryptDrivestasi tai palauttaa käyttöoikeuden käyttämällä uutta salasanaa.",
"drive_sfPasswordError": "Väärä salasana", "drive_sfPasswordError": "Väärä salasana",
"password_error_seed": "Padia ei löytynyt!<br>Tämä virhe voi johtua kahdesta syystä: joko padiin on lisätty tai vaihdettu salasana, tai padi on poistettu palvelimelta.", "password_error_seed": "Padia ei löytynyt!<br>Tämä virhe voi johtua kahdesta syystä: joko padiin on lisätty tai vaihdettu salasana, tai padi on poistettu palvelimelta.",
@ -876,8 +873,198 @@
"keywords": { "keywords": {
"title": "Avainsanat", "title": "Avainsanat",
"pad": { "pad": {
"q": "Mikä on padi?" "q": "Mikä on padi?",
"a": "<em>Padi</em> on <a href='http://etherpad.org/' target='_blank'>Etherpad-projektin</a> popularisoima termi reaaliaikaiselle kollaboratiiviselle editorille.\nSe tarkoittaa selaimessa muokattavaa dokumenttia, jossa muiden käyttäjien tekemät muutokset näkyvät lähes välittömästi."
},
"owned": {
"q": "Mikä on omistettu padi?",
"a": "<em>Omistettu padi</em> on padi, jolla on erityisesti määritelty <em>omistaja</em>, jonka palvelin tunnistaa <em>julkisen salausavaimen</em> perusteella. Padin omistaja voi poistaa omistamansa padit palvelimelta, jolloin muut yhteiskäyttäjät eivät voi enää käyttää niitä riippumatta siitä, olivatko ne tallennettuna heidän henkilökohtaisiin CryptDriveihinsa."
},
"expiring": {
"q": "Mikä on vanheneva padi?",
"a": "<em>Vanheneva padi</em> on padi, jolle on määritelty vanhenemisajankohta, jolloin padi poistetaan automaattisesti palvelimelta. Vanhenevat padit voidaan määritellä säilymään minkä tahansa ajan yhdestä tunnista 100 kuukauteen. Vanheneva padi ja sen historia muuttuvat vanhenemishetkellä pysyvästi käyttökelvottomiksi, vaikka padia muokattaisiinkin silloin.<br><br>Jos padi on määritelty vanhenevaksi, voit tarkastaa sen vanhenemisajan padin <em>ominaisuuksista</em> joko CryptDrivessa padin kohdalla hiiren oikealla painikkeella aukeavasta valikosta tai käyttämällä <em>Ominaisuudet-valikkoa</em> sovelluksen työkalupalkista."
},
"tag": {
"q": "Miten voin käyttää tunnisteita?",
"a": "Voit lisätä padeihin ja ladattuihin tiedostoihin tunnisteita CryptDrivessa tai käyttää <em>Tunniste</em>-painiketta (<span class='fa fa-hashtag'></span>) minkä tahansa editorin työkalupalkista. Hae padeja ja tiedostoja CryptDriven hakupalkista käyttämällä ristikkomerkillä alkavaa hakusanaa (esimerkiksi <em>#crypto</em>)."
},
"template": {
"q": "Mikä on mallipohja?",
"a": "Mallipohja on padi, jolla voit määritellä luotavan padin oletussisällön luodessasi toista samantyyppistä padia. Voit muuttaa minkä tahansa olemassaolevan padin mallipohjaksi siirtämällä sen <em>Mallipohjat</em>-osastoon CryptDrivessasi. Voit myös tehdä padista mallipohjana käytettävän kopion klikkaamalla Mallipohja-painiketta (<span class='fa fa-bookmark'></span>) editorin työkalupalkista."
},
"abandoned": {
"q": "Mikä on hylätty padi?",
"a": "<em>Hylätty padi</em> on padi, jota ei ole kiinnitetty yhdenkään rekisteröityneen käyttäjän CryptDriveen ja jota ei ole muokattu kuuteen kuukauteen. Hylätyt dokumentit poistetaan palvelimelta automaattisesti."
}
},
"privacy": {
"title": "Yksityisyys",
"different": {
"q": "Miten CryptPad eroaa muista padeja tarjoavista palveluista?",
"a": "CryptPad salaa padeihin tekemäsi muutokset ennen niiden lähettämistä palvelimelle, joten emme voi lukea, mitä kirjoitat."
},
"me": {
"q": "Mitä palvelin tietää minusta?",
"a": "Palvelimen ylläpitäjät näkevät CryptPadia käyttävien ihmisten IP-osoitteet. Emme pidä kirjaa siitä, mitkä osoitteet vierailevat missäkin padeissa. Tämä olisi kuitenkin teknisesti mahdollista, vaikka emme pääsekään tarkastelemaan padien salaamatonta sisältöä. Jos pelkäät meidän analysoivan näitä tietoja, on parasta olettaa meidän keräävän niitä, sillä emme voi todistaa, ettemme tee niin.<br><br>Keräämme käyttäjiltämme joitakin perustason telemetriatietoja, kuten käytetyn laitteen näytön koon ja tietoja useimmin käytetyistä painikkeista. Nämä auttavat meitä parantamaan CryptPadia, mutta jos et halua lähettää telemetriatietoja CryptPadille, voit <strong>jättäytyä pois tietojen keräämisestä ottamalla rastin pois <em>Salli käyttäjäpalaute</em>-ruudusta</strong>.<br><br>Pidämme kirjaa siitä, mitä padeja käyttäjät säilyttävät CryptDriveissaan pystyäksemme asettamaan tallennustilarajoituksia. Emme kuitenkaan tiedä näiden padien tyyppiä tai sisältöä. Tallennustilakiintiöt määritellään käyttäjien julkisten salausavainten perusteella, mutta emme yhdistä käyttäjien nimiä tai sähköpostiosoitteita näihin avaimiin.<br><br>Saadaksesi lisätietoja aiheesta voit tutustua kirjoittamaamme <a href='https://blog.cryptpad.fr/2017/07/07/cryptpad-analytics-what-we-cant-know-what-we-must-know-what-we-want-to-know/' target='_blank'>blogikirjoitukseen</a>."
},
"register": {
"q": "Jos rekisteröidyn, tietääkö palvelin minusta enemmän?",
"a": "Emme vaadi käyttäjiltä sähköpostiosoitteen vahvistusta, eikä palvelin saa tietää rekisteröitymisen yhteydessä edes käyttäjänimeäsi tai salasanaasi. Sen sijaan rekisteröitymis- ja sisäänkirjautumislomakkeet luovat antamastasi syötteestä uniikin avainrenkaan, ja palvelin saa tietoonsa ainoastaan kryptografisen allekirjoituksesi. Käytämme tätä tietoa yksityiskohtien, kuten tallennustilan käytön valvomiseen ja siten tallennustilakiintiöiden ylläpitämiseen.<br><br>Käytämme <em>palaute</em>-toimintoa kertoaksemme palvelimelle, että IP-osoitteestasi on luotu käyttäjätili. Tämä auttaa meitä pitämään kirjaa CryptPadiin rekisteröityneiden käyttäjien määrästä ja maantieteellisestä sijainnista, jotta voimme paremmin arvioida, mitä kieliä palvelumme kannattaisi tukea.<br><br>Rekisteröityneet käyttäjät kertovat palvelimelle, mitä padeja he säilyttävät CryptDriveissaan. Tämä on tarpeen, että kyseisiä padeja ei todeta hylätyiksi ja siten poisteta käyttämättömyyden takia."
},
"other": {
"q": "Mitä yhteistyökumppanit saavat tietää minusta?",
"a": "Muokatessasi padia jonkun toisen kanssa kaikki yhteydet kulkevat palvelimen kautta, joten vain me saamme tietää IP-osoitteesi. Muut käyttäjät näkevät näyttönimesi, avatar-kuvasi, linkin profiiliisi (jos olet luonut sellaisen) ja <em>julkisen salausavaimesi</em> (jota käytetään yhteyksien salaamiseen)."
},
"anonymous": {
"q": "Tekeekö CryptPad minusta anonyymin?",
"a": "Vaikka CryptPad on suunniteltu tietämään sinusta niin vähän kuin mahdollista, se ei tarjoa vahvaa anonymiteettisuojaa. Palvelimemme tietävät IP-osoitteesi, mutta voit halutessasi piilottaa sen käyttämällä CryptPadia Tor-verkosta. Pelkkä Tor-verkon käyttäminen ilman muutoksia verkkokäyttäytymiseesi ei takaa anonymiteettiä, sillä palvelin tunnistaa käyttäjät uniikkien salaustunnisteiden perusteella. Jos käytät samaa käyttäjätunnusta Tor-verkosta ja sen ulkopuolelta, istuntosi voidaan yhdistää sinuun.<br><br>Käyttäjille, joiden yksityisyysvaatimukset ovat matalammat - toisin kuin monet muut palvelut, CryptPad ei vaadi käyttäjiä tunnistautumaan nimellä, puhelinnumerolla tai sähköpostiosoitteella."
},
"policy": {
"q": "Onko teillä tietosuojakäytäntö?",
"a": "Kyllä! Se löytyy <a href='/privacy.html' target='_blank'>täältä</a>."
}
},
"security": {
"pad_password": {
"q": "Mitä tapahtuu, kun suojaan padin tai kansion salasanalla?",
"a": "Voit suojata minkä tahansa padin tai jaetun kansion salasanalla luodessasi sen. Voit myös käyttää Ominaisuudet-valikkoa asettaaksesi, vaihtaaksesi tai poistaaksesi salasanan milloin tahansa.<br><br>Padien ja jaettujen kansioiden salasanat on tarkoitettu suojaamaan linkkiä jakaessasi sitä mahdollisesti turvattomien kanavien, kuten sähköpostin tai tekstiviestin kautta. Jos joku onnistuu kaappaamaan linkkisi, mutta ei tiedä sen salasanaa, ei hän pääse lukemaan dokumenttiasi.<br><br>Kun jaat sisältöä CryptPadin sisällä yhteystietojesi tai tiimiesi kanssa, tiedonsiirto on salattua ja oletamme, että haluat heidän pääsevän käyttämään dokumenttiasi. Siksi salasana tallennetaan ja lähetetään padin mukana jakaessasi sitä CryptPadin sisällä. Vastaanottajalta tai sinulta itseltäsi <b>ei</b> pyydetä salasanaa dokumenttia avatessa."
},
"title": "Turvallisuus",
"proof": {
"q": "Miten käytätte nollatietotodistuksia (Zero Knowledge Proofs)?",
"a": "Käyttäessämme termiä \"nollatieto\" (Zero Knowledge) emme viittaa <em>nollatietotodistuksiin</em> (Zero Knowledge Proofs) vaan <em>nollatieto-verkkopalveluihin</em> (Zero Knowledge Web Services). Nollatieto-verkkopalvelut salaavat käyttäjän datan tämän selaimessa niin, ettei palvelin pääse missään vaiheessa käsittelemään salaamatonta dataa tai salausavaimia.<br><br>Olemme keränneet listan muista nollatietopalveluista <a href='https://blog.cryptpad.fr/2017/02/20/Time-to-Encrypt-the-Cloud/#Other-Zero-Knowledge-Services'>tänne</a>."
},
"why": {
"q": "Miksi minun kannattaisi käyttää CryptPadia?",
"a": "Mielestämme pilvipalveluiden ei tarvitse päästä lukemaan dataasi, jotta voit jakaa sen ystäviesi ja kollegoidesi kanssa. Jos käytät yhteistyöhön jotakin muuta palvelua, eikä palvelu erikseen ilmoita, ettei se pääse käsiksi tietoihisi, on hyvin todennäköistä, että tietojasi käytetään kaupallisiin tarkoituksiin."
},
"compromised": {
"q": "Suojaako CryptPad minua, jos laitteeni tietoturva on vaarantunut?",
"a": "Jos laitteesi varastetaan, CryptPad voi kirjata sinut ulos kaikista muista laitteista, paitsi nykyisestä laitteestasi. Tehdäksesi niin mene <strong>Asetukset</strong>-sivulle ja valitse <strong>Kirjaudu ulos kaikkialta</strong>. Kaikki muut tilillesi kirjautuneet aktiiviset laitteet kirjautuvat välittömästi ulos. Ne laitteet, joilla CryptPadia on käytetty aiemmin kirjautuvat ulos seuraavan sivunlatauksen yhteydessä.<br><br>Tällä hetkellä <em>etäuloskirjautuminen</em> on toteutettu selainpohjaisesti palvelimen sijaan. Näin ollen se ei suojaa sinua valtiollisilta toimijoilta, mutta on riittävä, jos unohdit kirjautua ulos CryptPadista käytettyäsi jaettua tietokonetta."
},
"crypto": {
"q": "Mitä kryptografisia menetelmiä käytätte?",
"a": "CryptPad perustuu kahteen avoimen lähdekoodin kryptografiakirjastoon: <a href='https://github.com/dchest/tweetnacl-js' target='_blank'>tweetnacl.js:n</a> ja <a href='https://github.com/dchest/scrypt-async-js' target='_blank'>scrypt-async.js:n</a>. <br><br>Scrypt on <em>salasanapohjainen avaimenmuodostusalgoritmi</em>. Käytämme sitä muuntaaksemme käyttäjätunnuksesi ja salasanasi uniikiksi avainrenkaaksi, joka turvaa pääsyn CryptDriveesi niin, että ainoastaan sinä pääset käsiksi padilistaasi. <br><br>Käytämme vastaavasti tweetnacl:n tarjoamia <em>xsalsa20-poly1305</em>- ja <em>x25519-xsalsa20-poly1305</em>-salakirjoitusjärjestelmiä salaamaan padeja ja keskusteluhistoriaa."
}
},
"usability": {
"title": "Käytettävyys",
"register": {
"q": "Mitä hyötyä rekisteröitymisestä on minulle?",
"a": "Rekisteröityneille käyttäjille on tarjolla joitakin toimintoja, jotka eivät ole saatavilla rekisteröitymättömille käyttäjille. Löydät nämä toiminnot <a href='/features.html' target='_blank'>luomastamme kaaviosta</a>."
},
"share": {
"q": "Miten jaan salattuja padeja kavereideni kanssa?",
"a": "CryptPad laittaa URL-osoitteessa padisi salaisen salausavaimen <em>#</em>-merkin jälkeen. Tämän merkin jälkeen laitettuja tietoja ei lähetetä palvelimelle, joten emme pääse koskaan käyttämään salausavaimiasi. Jakaessasi linkin padiin jaat oikeuden lukea ja käyttää sitä."
},
"remove": {
"q": "Poistin padin tai tiedoston CryptDrivestani, mutta sen sisältö on yhä käytettävissä. Miten voin poistaa sen?",
"a": "Ainoastaan <em>omistettuja padeja</em> (otettu käyttöön helmikuussa 2018) voi poistaa. Lisäksi nämä padit voi poistaa ainoastaan niiden <em>omistaja</em> eli henkilö, joka alun perin loi kyseisen padin. Jos et ole luonut kyseistä padia, joudut pyytämään sen omistajaa poistamaan sen puolestasi. Omistamiesi padien poistaminen onnistuu CryptDrivessa <strong>klikkaamalla padia hiiren oikealla painikkeella</strong> ja valitsemalla <strong>Poista palvelimelta</strong>."
},
"forget": {
"q": "Mitä tapahtuu, jos unohdan salasanani?",
"a": "Valitettavasti se, että pystyisimme palauttamaan käyttöoikeuden salattuihin padeihisi tarkoittaisi myös sitä, että pääsisimme itse käsiksi niiden sisältöön. Jos et kirjoittanut käyttäjätunnustasi ja salasanaasi ylös etkä muista kumpaakaan, voit mahdollisesti palauttaa padisi selaimesi historiaa suodattamalla."
},
"change": {
"q": "Entä jos haluan vaihtaa salasanani?",
"a": "Voit vaihtaa CryptPad-salasanasi Tilin asetukset-sivulta."
},
"devices": {
"q": "Olen kirjautunut sisään kahdella laitteella, ja näen kaksi eri CryptDrivea. Miten tämä on mahdollista?",
"a": "On todennäköistä, että olet rekisteröitynyt samalla käyttäjänimellä kahdesti eri salasanoja käyttäen. CryptPad-palvelin tunnistaa sinut kryptografisen allekirjoituksesi perusteella käyttäjänimen sijaan, joten se ei voi estää muita rekisteröitymästä samalla käyttäjänimellä. Tästä johtuen jokaisella käyttäjätilillä on ainutlaatuinen käyttäjänimen ja salasanan yhdistelmä. Sisäänkirjautuneet käyttäjät voivat nähdä käyttäjänimensä Asetukset-sivun ylälaidassa."
},
"folder": {
"q": "Voinko jakaa kokonaisia kansioita CryptDrivestani?",
"a": "Kyllä, voit luoda <em>jaetun kansion</em> CryptDrivestasi ja jakaa kerralla kaikki sen sisältämät padit."
},
"feature": {
"q": "Voitteko lisätä CryptPadiin tarvitsemani ominaisuuden?",
"a": "Monet CryptPadin ominaisuuksista ovat olemassa, koska käyttäjämme ovat toivoneet niitä. <a href='https://cryptpad.fr/contact.html' target='_blank'>Yhteystiedot-sivumme</a> kertoo, millä tavoin meihin saa yhteyden.<br><br>Valitettavasti emme voi taata, että pystymme toteuttamaan kaikki käyttäjiemme ehdotukset. Jos jokin tietty ominaisuus on kriittinen organisaatiosi kannalta, voit sponsoroida kehitystä varmistaaksesi sen toteutumisen. Ota yhteyttä osoitteeseen <a href='mailto:sales@cryptpad.fr' target='_blank'>sales@cryptpad.fr</a> saadaksesi lisätietoja.<br><br>Vaikka kehitystyön sponsorointi ei olisikaan mahdollista, olemme silti kiinnostuneita palautteesta, joka auttaa meitä parantamaan CryptPadia. Ota meihin milloin tahansa yhteyttä yllä luetelluilla tavoilla."
}
},
"other": {
"title": "Muita kysymyksiä",
"pay": {
"q": "Miksi minun täytyisi maksaa, kun niin monet toiminnot ovat ilmaisia?",
"a": "Annamme tukijoillemme lisätallennustilaa ja mahdollisuuden kasvattaa kavereiden tallennustilakiintiöitä (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>lue lisää</a>).<br><br>Näiden lyhytaikaisten etujen lisäksi premium-tilaus auttaa rahoittamaan CryptPadin jatkuvaa, aktiivista kehitystyötä. Tähän kuuluu bugien korjaamista, uusien ominaisuuksien lisäämistä ja CryptPad-instanssien pystyttämisen ja ylläpidon helpottamista. Lisäksi autat näyttämään muille palveluntarjoajille, että ihmiset ovat valmiita tukemaan yksityisyyttä parantavia teknologioita. Toivomme, että käyttäjätietojen myymiseen perustuvat liiketoimintamallit jäävät lopulta menneeseen.<br><br>Lopuksi, tarjoamme suurimman osan CryptPadin toiminnallisuudesta ilmaiseksi, koska uskomme yksityisyyden kuuluvan kaikille - ei vain niille, joilla on varaa maksaa siitä. Tukemalla meitä autat tarjoamaan heikommassa asemassa oleville väestöille pääsyn näihin peruspalveluihin."
},
"goal": {
"q": "Mitkä ovat tavoitteenne?",
"a": "Kehittämällä yksityisyyttä kunnioittavaa kollaboraatioteknologiaa toivomme nostavamme käyttäjien odotuksia pilvipalveluiden yksityisyyden suhteen. Toivomme, että työmme rohkaisee muita palveluntarjoajia pyrkimään samaan tai parempaan lopputulokseen. Optimismistamme huolimatta tiedämme, että suuri osa webistä rahoitetaan kohdistetulla mainonnalla. Tehtävää on paljon enemmän, kuin mihin pystymme yksin - arvostamme yhteisömme tarjoamaa mainostusta, tukea ja panosta tavoitteidemme saavuttamisessa."
},
"jobs": {
"q": "Etsittekö työntekijöitä?",
"a": "Kyllä! Esittäydy meille sähköpostilla osoitteeseen <a href='mailto:jobs@xwiki.com' target='_blank'>jobs@xwiki.com</a>."
},
"host": {
"q": "Voitteko auttaa minua perustamaan oman CryptPad-instanssini?",
"a": "Tarjoamme mielellämme tukea organisaatiosi sisäiselle CryptPad-instanssille. Ota yhteyttä osoitteeseen <a href='mailto:sales@cryptpad.fr' target='_blank'>sales@cryptpad.fr</a> saadaksesi lisätietoja."
},
"revenue": {
"q": "Kuinka voin osallistua tulojen jakamiseen?",
"a": "Jos ylläpidät omaa CryptPad-instanssiasi, haluaisit ottaa käyttöön maksulliset käyttäjätilit ja jakaa tulot CryptPadin kehittäjien kanssa, palvelimesi täytyy määritellä kumppanipalveluksi.<br><br>CryptPad-asennushakemistosi <em>config.example.js</em>-tiedostosta pitäisi löytyä ohjeet tämän palvelun käyttöönottoon. Sinun tulee myös ottaa yhteyttä osoitteeseen <a href='mailto:sales@cryptpad.fr'>sales@cryptpad.fr</a> varmistaaksesi, että palvelimesi HTTPS-määritykset ovat kunnossa ja sopiaksesi käytettävistä maksutavoista."
} }
} }
} },
"policy_howweuse_p1": "Käytämme näitä tietoja suunnitellaksemme CryptPadin mainostusta ja arvioidaksemme aiempien kampanjoiden onnistumista. Sijaintitietosi puolestaan kertovat meille, mitä kieliä CryptPadin tulisi mahdollisesti tukea englannin lisäksi.",
"tos_title": "CryptPad-käyttöehdot",
"tos_legal": "Älä ole pahantahtoinen, väärinkäyttäjä tai tee mitään laitonta.",
"tos_availability": "Toivomme sinun pitävän tätä palvelua hyödyllisenä, mutta emme voi taata sen saatavuutta tai suorituskykyä. Viethän tietosi säännöllisesti muualle talteen.",
"tos_e2ee": "CryptPad-sisältöä voi lukea tai muokata kuka tahansa, joka pystyy arvaamaan tai muuten saamaan käsiinsä padin katkelmatunnisteen. Suosittelemme käyttämään päästä päähän salattuja viestintämenetelmiä linkkien jakamiseen, emmekä ota vastuuta tilanteissa, joissa sellainen linkki pääsee vuotamaan.",
"tos_logs": "Selaimesi palvelimelle tarjoama metadata voidaan kerätä palvelun ylläpitämistä varten.",
"tos_3rdparties": "Emme luovuta yksilöityä dataa kolmansille osapuolille, ellei meillä ole lakisääteistä velvollisuutta tehdä niin.",
"four04_pageNotFound": "Etsimääsi sivua ei löytynyt.",
"updated_0_header_logoTitle": "Siirry CryptDriveesi",
"header_logoTitle": "Siirry CryptDriveesi",
"header_homeTitle": "Siirry CryptPad-kotisivulle",
"help": {
"title": "Näin pääset alkuun",
"generic": {
"more": "Tutustu <a href=\"/faq.html\" target=\"_blank\">usein kysyttyihin kysymyksiin</a> saadaksesi lisätietoja CryptPadin toiminnallisuudesta",
"share": "Käytä Jaa-valikkoa (<span class=\"fa fa-shhare-alt\"></span>) luodaksesi linkin, jonka kautta yhteistyökumppanit pääsevät katselemaan tai muokkaamaan padia",
"save": "Kaikki tekemäsi muutokset synkronoidaan automaattisesti, joten sinun ei tarvitse koskaan tallentaa"
},
"text": {
"formatting": "Voit näyttää tai piilottaa Tekstin muotoilu-työkalupalkin klikkaamalla <span class=\"fa fa-caret-down\"></span> tai <span class=\"fa fa-caret-up\"></span>-painikkeita",
"embed": "Rekisteröityneet käyttäjät voivat upottaa kuvan tai CryptDriveen tallennetun tiedoston <span class=\"fa fa-image\"></span> avulla",
"history": "Voit käyttää <em>historiaa</em> <span class=\"fa fa-history\"></span> katsellaksesi tai palauttaaksesi aiempia versioita"
},
"pad": {
"export": "Voit viedä sisältösi PDF-tiedostoon Tekstin muotoilu-työkalupalkin <span class=\"fa fa-print\"></span> -painikkeella"
},
"code": {
"modes": "Käytä <span class=\"fa fa-ellipsis-h\"></span> -alavalikon pudotusvalikoita vaihtaaksesi syntaksin korostustilaa tai väriteemoja"
},
"beta": {
"warning": "Tämä editori on edelleen <strong>koekäytössä</strong>, voit ilmoittaa löytämäsi bugit <a href=\"https://github.com/xwiki-labs/cryptpad/issues/\" target=\"_blank\">asianhallintajärjestelmäämme</a>"
},
"oo": {
"access": "Käyttö on rajattu ainoastaan rekisteröityneille käyttäjille, yhteistyökumppanien tulee kirjautua sisään"
},
"slide": {
"markdown": "Kirjoita diat <a href=\"http://www.markdowntutorial.com/\">Markdown-kielellä</a> ja erota ne toisistaan <code>---</code> -rivillä",
"present": "Aloita esitys <span class=\"fa fa-play-circle\"></span> -painikkeella",
"settings": "Muuta dian asetuksia (taustakuvaa, siirtymiä, sivunumeroita jne.) <span class=\"fa fa-ellipsis-h\"></span> -alavalikon <span class=\"fa fa-cog\"></span> -painikkeella",
"colors": "Vaihda tekstin ja taustan väriä <span class=\"fa fa-i-cursor\"></span> ja <span class=\"fa fa-square\"></span> -painikkeilla"
},
"poll": {
"decisions": "Tee päätöksiä luotettujen ystävien kesken",
"options": "Ehdota vaihtoehtoja ja tuo ilmi mielipiteesi",
"choices": "Napsauta sarakkeesi soluja valitaksesi kyllä- (<strong>✔</strong>), ehkä- (<strong>~</strong>), tai ei (<strong>✖</strong>) -vaihtoehdon",
"submit": "Napsauta <strong>Lähetä</strong> tehdäksesi valintasi näkyviksi muille"
},
"whiteboard": {
"colors": "Kaksoisnapsauta värejä muokataksesi väripalettiasi",
"mode": "Ota piirtotila pois käytöstä vetääksesi ja venyttääksesi viivoja",
"embed": "Upota kuvia kovalevyltäsi <span class=\"fa fa-file-image-o\"></span> tai CryptDrivestasi <span class=\"fa fa-image\"></span> ja vie ne PNG-tiedostomuodossa kovalevyllesi <span class=\"fa fa-download\"></span> tai CryptDriveesi <span class=\"fa fa-cloud-upload\"></span>"
},
"kanban": {
"add": "Lisää uusia tauluja oikeassa yläkulmassa olevalla <span class=\"fa fa-plus\"></span> -painikkeella",
"task": "Siirrä kohtia raahaamalla ja pudottamalla ne yhdestä taulusta toiseen",
"color": "Vaihda värejä napsauttamalla taulun otsikon vieressä olevaa värillistä osaa"
}
},
"driveReadmeTitle": "Mikä on CryptPad?",
"readme_welcome": "Tervetuloa CryptPadiin!",
"readme_p1": "Tervetuloa CryptPadiin, täällä voit tehdä muistiinpanoja yksin tai ystäviesi kanssa."
} }

@ -12,7 +12,7 @@
"media": "Média", "media": "Média",
"todo": "Todo", "todo": "Todo",
"contacts": "Contacts", "contacts": "Contacts",
"sheet": "Tableur (Beta)", "sheet": "Tableur",
"teams": "Équipes" "teams": "Équipes"
}, },
"button_newpad": "Nouveau document texte", "button_newpad": "Nouveau document texte",
@ -279,7 +279,7 @@
"profile_description": "Description", "profile_description": "Description",
"profile_fieldSaved": "Nouvelle valeur enregistrée : {0}", "profile_fieldSaved": "Nouvelle valeur enregistrée : {0}",
"profile_viewMyProfile": "Voir mon profil", "profile_viewMyProfile": "Voir mon profil",
"userlist_addAsFriendTitle": "Envoyer une demande d'ami à « {0} »", "userlist_addAsFriendTitle": "Envoyer une demande de contact à « {0} »",
"contacts_title": "Contacts", "contacts_title": "Contacts",
"contacts_addError": "Erreur lors de l'ajout de ce contact dans votre liste", "contacts_addError": "Erreur lors de l'ajout de ce contact dans votre liste",
"contacts_added": "Invitation de contact acceptée.", "contacts_added": "Invitation de contact acceptée.",
@ -299,7 +299,7 @@
"contacts_confirmRemoveHistory": "Êtes-vous sûr de vouloir supprimer définitivement l'historique de votre chat ? Les messages ne pourront pas être restaurés.", "contacts_confirmRemoveHistory": "Êtes-vous sûr de vouloir supprimer définitivement l'historique de votre chat ? Les messages ne pourront pas être restaurés.",
"contacts_removeHistoryServerError": "Une erreur est survenue lors de la supprimer de l'historique du chat. Veuillez réessayer plus tard.", "contacts_removeHistoryServerError": "Une erreur est survenue lors de la supprimer de l'historique du chat. Veuillez réessayer plus tard.",
"contacts_fetchHistory": "Récupérer l'historique plus ancien", "contacts_fetchHistory": "Récupérer l'historique plus ancien",
"contacts_friends": "Amis", "contacts_friends": "Contacts",
"contacts_rooms": "Salons", "contacts_rooms": "Salons",
"contacts_leaveRoom": "Quitter ce salon", "contacts_leaveRoom": "Quitter ce salon",
"contacts_online": "Un autre utilisateur est en ligne dans ce salon", "contacts_online": "Un autre utilisateur est en ligne dans ce salon",
@ -671,7 +671,7 @@
"features_f_social": "Applications sociales", "features_f_social": "Applications sociales",
"features_f_social_note": "Créer un profil, utiliser un avatar, chat avec les contacts", "features_f_social_note": "Créer un profil, utiliser un avatar, chat avec les contacts",
"features_f_file1": "Importer et partager des fichiers", "features_f_file1": "Importer et partager des fichiers",
"features_f_file1_note": "Partager des fichiers avec vos amis ou les intégrer dans vos pads", "features_f_file1_note": "Partager des fichiers avec vos contacts ou les intégrer dans vos pads",
"features_f_storage1": "Stockage permanent (50Mo)", "features_f_storage1": "Stockage permanent (50Mo)",
"features_f_storage1_note": "Les pads stockés dans votre CryptDrive ne seront jamais supprimés pour cause d'inactivité", "features_f_storage1_note": "Les pads stockés dans votre CryptDrive ne seront jamais supprimés pour cause d'inactivité",
"features_f_register": "S'enregistrer gratuitement", "features_f_register": "S'enregistrer gratuitement",
@ -774,7 +774,7 @@
"a": "Les utilisateurs enregistrés ont accès à un certain nombre de nouvelles fonctionnalités inaccessibles aux utilisateurs non connectés. Un tableau récapitulatif est disponible <a href=\"/features.html\">ici</a>." "a": "Les utilisateurs enregistrés ont accès à un certain nombre de nouvelles fonctionnalités inaccessibles aux utilisateurs non connectés. Un tableau récapitulatif est disponible <a href=\"/features.html\">ici</a>."
}, },
"share": { "share": {
"q": "Comment partager des pads chiffrés avec mes amis ?", "q": "Comment partager des pads chiffrés avec mes contacts ?",
"a": "CryptPad stocke la clé secrète de chiffrement des pads après le symbole <em>#</em> dans l'URL. Tout ce qui se trouve après ce symbole n'est jamais envoyé au serveur, ainsi nous n'avons pas accès à vos clés de chiffrement. Partager le lien d'un pad revient donc à permettre la lecture ou la modification du contenu." "a": "CryptPad stocke la clé secrète de chiffrement des pads après le symbole <em>#</em> dans l'URL. Tout ce qui se trouve après ce symbole n'est jamais envoyé au serveur, ainsi nous n'avons pas accès à vos clés de chiffrement. Partager le lien d'un pad revient donc à permettre la lecture ou la modification du contenu."
}, },
"remove": { "remove": {
@ -806,7 +806,7 @@
"title": "Autres questions", "title": "Autres questions",
"pay": { "pay": {
"q": "Pourquoi payer alors que toutes les fonctionnalités sont gratuites ?", "q": "Pourquoi payer alors que toutes les fonctionnalités sont gratuites ?",
"a": "Un compte premium permet d'<b>augmenter la limite de stockage</b> dans le CryptDrive, ainsi que celle de ses amis (<a href=\"https://accounts.cryptpad.fr/#/faq\" target=\"_blank\">en savoir plus</a>).<br>En plus de ces avantages directs, l'abonnement premium permet aussi de <b>financer le développement</b> actif et de manière continue de CryptPad. Cela comprend la correction de bugs, l'ajout de nouvelles fonctionnalités et rendre plus facile l'hébergement de CryptPad par d'autres personnes.<br>Avec un abonnement, vous aidez aussi à prouver aux autres fournisseurs de services que les gens sont prêts à supporter les technologies améliorant le respect de leur vie privée. Nous espérons qu'un jour, les entreprises ayant pour revenu principal la revente de données des utilisateurs soient de l'histoire ancienne.<br>Enfin, nous offrons la plupart des fonctionnalités gratuitement parce que nous croyons que tout le monde mérite le respect de la vie privée. En souscrivant à un compte premium, vous nous aider à maintenir ces fonctionnalités basiques accessibles aux populations défavorisées." "a": "Un compte premium permet d'<b>augmenter la limite de stockage</b> dans le CryptDrive, ainsi que celle de ses contacts (<a href=\"https://accounts.cryptpad.fr/#/faq\" target=\"_blank\">en savoir plus</a>).<br>En plus de ces avantages directs, l'abonnement premium permet aussi de <b>financer le développement</b> actif et de manière continue de CryptPad. Cela comprend la correction de bugs, l'ajout de nouvelles fonctionnalités et rendre plus facile l'hébergement de CryptPad par d'autres personnes.<br>Avec un abonnement, vous aidez aussi à prouver aux autres fournisseurs de services que les gens sont prêts à supporter les technologies améliorant le respect de leur vie privée. Nous espérons qu'un jour, les entreprises ayant pour revenu principal la revente de données des utilisateurs soient de l'histoire ancienne.<br>Enfin, nous offrons la plupart des fonctionnalités gratuitement parce que nous croyons que tout le monde mérite le respect de la vie privée. En souscrivant à un compte premium, vous nous aider à maintenir ces fonctionnalités basiques accessibles aux populations défavorisées."
}, },
"goal": { "goal": {
"q": "Quel est votre objectif ?", "q": "Quel est votre objectif ?",
@ -885,7 +885,7 @@
}, },
"driveReadmeTitle": "Qu'est-ce que CryptPad ?", "driveReadmeTitle": "Qu'est-ce que CryptPad ?",
"readme_welcome": "Bienvenue dans CryptPad !", "readme_welcome": "Bienvenue dans CryptPad !",
"readme_p1": "Bienvenue dans CryptPad, le lieu où vous pouvez prendre des notes seul ou avec des amis.", "readme_p1": "Bienvenue dans CryptPad, le lieu où vous pouvez prendre des notes seul ou avec des contacts.",
"readme_p2": "Ce pad va vous donner un aperçu de la manière dont vous pouvez utiliser CryptPad pour prendre des notes, les organiser et travailler en groupe sur celles-ci.", "readme_p2": "Ce pad va vous donner un aperçu de la manière dont vous pouvez utiliser CryptPad pour prendre des notes, les organiser et travailler en groupe sur celles-ci.",
"readme_cat1": "Découvrez votre CryptDrive", "readme_cat1": "Découvrez votre CryptDrive",
"readme_cat1_l1": "Créer un pad : Dans votre CryptDrive, cliquez sur {0} puis {1} et vous obtenez un nouveau pad.", "readme_cat1_l1": "Créer un pad : Dans votre CryptDrive, cliquez sur {0} puis {1} et vous obtenez un nouveau pad.",
@ -1058,18 +1058,17 @@
"friendRequest_later": "Décider plus tard", "friendRequest_later": "Décider plus tard",
"friendRequest_accept": "Accepter (Entrée)", "friendRequest_accept": "Accepter (Entrée)",
"friendRequest_decline": "Décliner", "friendRequest_decline": "Décliner",
"friendRequest_declined": "<b>{0}</b> a décliné votre demande d'ami", "friendRequest_declined": "<b>{0}</b> a décliné votre demande de contact",
"friendRequest_accepted": "<b>{0}</b> a accepté votre demande d'ami", "friendRequest_accepted": "<b>{0}</b> a accepté votre demande de contact",
"friendRequest_received": "<b>{0}</b> souhaite être votre ami", "friendRequest_received": "<b>{0}</b> souhaite être votre contact",
"friendRequest_notification": "<b>{0}</b> vous a envoyé une demande d'ami", "friendRequest_notification": "<b>{0}</b> vous a envoyé une demande de contact",
"notifications_empty": "Pas de nouvelle notification", "notifications_empty": "Pas de nouvelle notification",
"notifications_title": "Vous avez des notifications non lues", "notifications_title": "Vous avez des notifications non lues",
"profile_addDescription": "Ajouter une description", "profile_addDescription": "Ajouter une description",
"profile_editDescription": "Modifier votre description", "profile_editDescription": "Modifier votre description",
"profile_addLink": "Ajouter un lien vers votre site web", "profile_addLink": "Ajouter un lien vers votre site web",
"profile_info": "Les autres utilisateurs peuvent trouver votre profil en cliquant sur votre nom dans la liste d'utilisateurs des documents.", "profile_info": "Les autres utilisateurs peuvent trouver votre profil en cliquant sur votre nom dans la liste d'utilisateurs des documents.",
"profile_friendRequestSent": "Demande d'ami en attente...", "profile_friendRequestSent": "Demande de contact en attente...",
"profile_friend": "{0} est votre ami(e)",
"notification_padShared": "{0} a partagé un pad avec vous : <b>{1}</b>", "notification_padShared": "{0} a partagé un pad avec vous : <b>{1}</b>",
"notification_fileShared": "{0} a partagé un fichier avec vous : <b>{1}</b>", "notification_fileShared": "{0} a partagé un fichier avec vous : <b>{1}</b>",
"notification_folderShared": "{0} a partagé un dossier avec vous : <b>{1}</b>", "notification_folderShared": "{0} a partagé un dossier avec vous : <b>{1}</b>",
@ -1080,7 +1079,7 @@
"share_withFriends": "Partager", "share_withFriends": "Partager",
"notifications_dismiss": "Cacher", "notifications_dismiss": "Cacher",
"fm_info_sharedFolderHistory": "Vous regardez l'historique de votre dossier partagé <b>{0}</b><br/>Votre CryptDrive restera en lecture seule pendant la navigation.", "fm_info_sharedFolderHistory": "Vous regardez l'historique de votre dossier partagé <b>{0}</b><br/>Votre CryptDrive restera en lecture seule pendant la navigation.",
"share_description": "Choisissez ce que vous souhaitez partager puis obtenez le lien ou envoyez-le directement à vos amis CryptPad.", "share_description": "Choisissez ce que vous souhaitez partager puis obtenez le lien ou envoyez-le directement à vos contacts CryptPad.",
"fc_color": "Changer la couleur", "fc_color": "Changer la couleur",
"supportPage": "Support", "supportPage": "Support",
"admin_cat_support": "Support", "admin_cat_support": "Support",
@ -1108,7 +1107,7 @@
"notificationsPage": "Notifications", "notificationsPage": "Notifications",
"openNotificationsApp": "Ouvrir le panneau de notifications", "openNotificationsApp": "Ouvrir le panneau de notifications",
"notifications_cat_all": "Toutes", "notifications_cat_all": "Toutes",
"notifications_cat_friends": "Demandes d'ami", "notifications_cat_friends": "Demandes de contact",
"notifications_cat_pads": "Partagé avec moi", "notifications_cat_pads": "Partagé avec moi",
"notifications_cat_archived": "Historique", "notifications_cat_archived": "Historique",
"notifications_dismissAll": "Tout cacher", "notifications_dismissAll": "Tout cacher",
@ -1122,8 +1121,6 @@
"requestEdit_button": "Demander les droits d'édition", "requestEdit_button": "Demander les droits d'édition",
"requestEdit_dialog": "Êtes-vous sûr de vouloir demander les droits d'édition de ce pad au propriétaire ?", "requestEdit_dialog": "Êtes-vous sûr de vouloir demander les droits d'édition de ce pad au propriétaire ?",
"requestEdit_confirm": "{1} a demandé les droits d'édition pour le pad <b>{0}</b>. Souhaitez-vous leur accorder les droits ?", "requestEdit_confirm": "{1} a demandé les droits d'édition pour le pad <b>{0}</b>. Souhaitez-vous leur accorder les droits ?",
"requestEdit_fromFriend": "Vous êtes amis avec {0}",
"requestEdit_fromStranger": "Vous n'êtes <b>pas</b> amis avec {0}",
"requestEdit_viewPad": "Ouvrir le pad dans un nouvel onglet", "requestEdit_viewPad": "Ouvrir le pad dans un nouvel onglet",
"later": "Décider plus tard", "later": "Décider plus tard",
"requestEdit_request": "{1} souhaite éditer le pad <b>{0}</b>", "requestEdit_request": "{1} souhaite éditer le pad <b>{0}</b>",
@ -1150,7 +1147,7 @@
"register_emailWarning3": "Si vous souhaitez tout de même utiliser votre adresse email comme nom d'utilisateur, appuyez sur OK.", "register_emailWarning3": "Si vous souhaitez tout de même utiliser votre adresse email comme nom d'utilisateur, appuyez sur OK.",
"owner_removeText": "Supprimer un propriétaire existant", "owner_removeText": "Supprimer un propriétaire existant",
"owner_removePendingText": "Annuler une offre en attente", "owner_removePendingText": "Annuler une offre en attente",
"owner_addText": "Proposer à un ami d'être co-propriétaire de ce document", "owner_addText": "Proposer à un contact d'être co-propriétaire de ce document",
"owner_unknownUser": "Utilisateur inconnu", "owner_unknownUser": "Utilisateur inconnu",
"owner_removeButton": "Supprimer les propriétaires sélectionnés", "owner_removeButton": "Supprimer les propriétaires sélectionnés",
"owner_removePendingButton": "Annuler les offres sélectionnées", "owner_removePendingButton": "Annuler les offres sélectionnées",
@ -1167,9 +1164,8 @@
"owner_removedPending": "{0} a annulé l'offre de co-propriété reçue pour <b>{1}</b>", "owner_removedPending": "{0} a annulé l'offre de co-propriété reçue pour <b>{1}</b>",
"padNotPinnedVariable": "Ce pad va expirer après {4} jours d'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.", "padNotPinnedVariable": "Ce pad va expirer après {4} jours d'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.",
"share_linkTeam": "Ajouter au CryptDrive d'une équipe", "share_linkTeam": "Ajouter au CryptDrive d'une équipe",
"team_pickFriends": "Choisissez les amis à inviter dans cette équipe", "team_pickFriends": "Choisissez les contacts à inviter dans cette équipe",
"team_inviteModalButton": "Inviter", "team_inviteModalButton": "Inviter",
"team_noFriend": "Vous n'avez pas encore ajouté d'ami sur CryptPad.",
"team_pcsSelectLabel": "Sauver dans", "team_pcsSelectLabel": "Sauver dans",
"team_pcsSelectHelp": "Créer un pad dans le drive d'une équipe rend cette équipe propriétaire du pad si l'option est cochée.", "team_pcsSelectHelp": "Créer un pad dans le drive d'une équipe rend cette équipe propriétaire du pad si l'option est cochée.",
"team_invitedToTeam": "{0} vous à inviter à rejoindre l'équipe : <b>{1}</b>", "team_invitedToTeam": "{0} vous à inviter à rejoindre l'équipe : <b>{1}</b>",
@ -1191,7 +1187,7 @@
"team_rosterPromote": "Promouvoir", "team_rosterPromote": "Promouvoir",
"team_rosterDemote": "Rétrograder", "team_rosterDemote": "Rétrograder",
"team_rosterKick": "Expulser de l'équipe", "team_rosterKick": "Expulser de l'équipe",
"team_inviteButton": "Inviter des amis", "team_inviteButton": "Inviter des contacts",
"team_leaveButton": "Quitter cette équipe", "team_leaveButton": "Quitter cette équipe",
"team_leaveConfirm": "Si vous quittez cette équipe, vous perdrez l'accès à son CryptDrive, son chat et les autres contenus. Êtes-vous sûr ?", "team_leaveConfirm": "Si vous quittez cette équipe, vous perdrez l'accès à son CryptDrive, son chat et les autres contenus. Êtes-vous sûr ?",
"team_owner": "Propriétaires", "team_owner": "Propriétaires",
@ -1294,5 +1290,8 @@
"oo_exportInProgress": "Exportation en cours", "oo_exportInProgress": "Exportation en cours",
"oo_sheetMigration_loading": "Mise à jour de la feuille de calcul", "oo_sheetMigration_loading": "Mise à jour de la feuille de calcul",
"oo_sheetMigration_complete": "Version mise à jour disponible, appuyez sur OK pour recharger.", "oo_sheetMigration_complete": "Version mise à jour disponible, appuyez sur OK pour recharger.",
"oo_sheetMigration_anonymousEditor": "L'édition de cette feuille de calcul est désactivée pour les utilisateurs anonymes jusqu'à ce qu'elle soit mise à jour par un utilisateur enregistré." "oo_sheetMigration_anonymousEditor": "L'édition de cette feuille de calcul est désactivée pour les utilisateurs anonymes jusqu'à ce qu'elle soit mise à jour par un utilisateur enregistré.",
"imprint": "Mentions légales",
"isContact": "{0} est dans vos contacts",
"isNotContact": "{0} n'est <b>pas</b> dans vos contacts"
} }

@ -1,8 +1,8 @@
{ {
"main_title": "CryptPad: Editor collaborativo in tempo reale, zero knowledge", "main_title": "CryptPad: Editor zero knowledge collaborativo in tempo reale",
"type": { "type": {
"pad": "Testo", "pad": "Testo",
"code": "Code", "code": "Codice",
"poll": "Sondaggio", "poll": "Sondaggio",
"kanban": "Kanban", "kanban": "Kanban",
"slide": "Presentazione", "slide": "Presentazione",
@ -10,13 +10,13 @@
"whiteboard": "Lavagna", "whiteboard": "Lavagna",
"file": "File", "file": "File",
"media": "Media", "media": "Media",
"todo": "Todo", "todo": "Promemoria",
"contacts": "Contatti", "contacts": "Contatti",
"sheet": "Fogli (Beta)", "sheet": "Fogli",
"teams": "Team" "teams": "Team"
}, },
"button_newpad": "Nuovo pad di Testo", "button_newpad": "Nuovo pad di Testo",
"button_newcode": "Nuovo pad di Code", "button_newcode": "Nuovo pad di Codice",
"button_newpoll": "Nuovo Sondaggio", "button_newpoll": "Nuovo Sondaggio",
"button_newslide": "Nuova Presentazione", "button_newslide": "Nuova Presentazione",
"button_newwhiteboard": "Nuova Lavagna", "button_newwhiteboard": "Nuova Lavagna",
@ -34,7 +34,7 @@
"inactiveError": "Questo pad è stato cancellato per inattività. Premi Esc per creare un nuovo pad.", "inactiveError": "Questo pad è stato cancellato per inattività. Premi Esc per creare un nuovo pad.",
"chainpadError": "Si è verificato un errore critico nell'aggiornamento del tuo contenuto. Questa pagina è in modalità solo lettura per assicurarci che non perderai il tuo lavoro..<br>Premi <em>Esc</em> per continuare a visualizzare questo pad, o ricarica la pagina per provare a modificarlo di nuovo.", "chainpadError": "Si è verificato un errore critico nell'aggiornamento del tuo contenuto. Questa pagina è in modalità solo lettura per assicurarci che non perderai il tuo lavoro..<br>Premi <em>Esc</em> per continuare a visualizzare questo pad, o ricarica la pagina per provare a modificarlo di nuovo.",
"invalidHashError": "Il documento richiesto ha un URL non valido.", "invalidHashError": "Il documento richiesto ha un URL non valido.",
"errorCopy": " Puoi ancora copiare il contenuto altrove premendo <em>Esc</em>.<br>Una volta abbandonata questa pagina, non sarà possibile recuperarlo!", "errorCopy": " Puoi ancora accedere al contenuto premendo <em>Esc</em>.<br>Una volta chiusa questa finestra, non sarà possibile accedere di nuovo.",
"errorRedirectToHome": "Premi <em>Esc</em> per essere reindirizzato al tuo CryptDrive.", "errorRedirectToHome": "Premi <em>Esc</em> per essere reindirizzato al tuo CryptDrive.",
"newVersionError": "Una nuova versione di CryptPad è disponibile. <br><a href='#'>Ricarica</a> per usare la nuova versione, o premi Esc per accedere al contenuto in <b>modalità offline</b>.", "newVersionError": "Una nuova versione di CryptPad è disponibile. <br><a href='#'>Ricarica</a> per usare la nuova versione, o premi Esc per accedere al contenuto in <b>modalità offline</b>.",
"loading": "Caricamento...", "loading": "Caricamento...",
@ -447,19 +447,19 @@
"settings_exportTitle": "Esporta il tuo CryptDrive", "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_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.", "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.",
"settings_exportWarning": "", "settings_exportWarning": "Nota bene: questo strumento è ancora in versione beta e può presentare problemi di scalabilità. Per migliorare le prestazioni, è consigliabile lasciare attiva questa tab.",
"settings_exportCancel": "", "settings_exportCancel": "Sei sicuro di voler cancellare l'export? Dovrai iniziare da capo la prossima volta.",
"settings_export_reading": "", "settings_export_reading": "Lettura del tuo CryptDrive in corso...",
"settings_export_download": "", "settings_export_download": "Scaricamento e decriptazione dei tuoi documenti in corso...",
"settings_export_compressing": "", "settings_export_compressing": "Compressione dei dati in corso...",
"settings_export_done": "", "settings_export_done": "Il tuo download è pronto!",
"settings_exportError": "", "settings_exportError": "Visualizza errori",
"settings_exportErrorDescription": "", "settings_exportErrorDescription": "Non siamo riusciti ad aggiungere i seguenti documenti all'export:",
"settings_exportErrorEmpty": "", "settings_exportErrorEmpty": "Questo documento non può essere esportato (contenuto vuoto o invalido).",
"settings_exportErrorMissing": "", "settings_exportErrorMissing": "Questo documento non è stato trovato nei nostri server (scaduto o rimosso dal suo proprietario)",
"settings_exportErrorOther": "", "settings_exportErrorOther": "È accaduto un errore durante l'esportazione di questo documento: {0}",
"settings_resetNewTitle": "Pulisci CryptDrive", "settings_resetNewTitle": "Pulisci CryptDrive",
"settings_resetButton": "", "settings_resetButton": "Rimuovi",
"settings_reset": "Rimuovi tutti i file e le cartelle dal tuo CryptDrive", "settings_reset": "Rimuovi tutti i file e le cartelle dal tuo CryptDrive",
"settings_resetPrompt": "", "settings_resetPrompt": "",
"settings_resetDone": "", "settings_resetDone": "",
@ -513,5 +513,9 @@
}, },
"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", "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", "settings_codeSpellcheckLabel": "Abilita la revisione ortografica nell'editor di codice",
"team_inviteLinkError": "Si è verificato un errore durante la creazione del link." "team_inviteLinkError": "Si è verificato un errore durante la creazione del link.",
"register_emailWarning1": "Puoi farlo se vuoi, ma non verrà inviato ai nostri server.",
"register_emailWarning2": "Non sarai in grado di resettare la tua password usando la tua email, a differenza di come puoi fare con molti altri servizi.",
"register_emailWarning3": "Se hai capito, ma intendi comunque usare la tua email come nome utente, clicca OK.",
"oo_sheetMigration_anonymousEditor": "Le modifiche da parte di utenti anonimi a questo foglio di calcolo sono disabilitate finchè un utente registrato non lo aggiorna all'ultima versione."
} }

@ -282,7 +282,7 @@
"profile_description": "Description", "profile_description": "Description",
"profile_fieldSaved": "New value saved: {0}", "profile_fieldSaved": "New value saved: {0}",
"profile_viewMyProfile": "View my profile", "profile_viewMyProfile": "View my profile",
"userlist_addAsFriendTitle": "Send \"{0}\" a friend request", "userlist_addAsFriendTitle": "Send \"{0}\" a contact request",
"contacts_title": "Contacts", "contacts_title": "Contacts",
"contacts_addError": "Error while adding that contact to the list", "contacts_addError": "Error while adding that contact to the list",
"contacts_added": "Contact invite accepted.", "contacts_added": "Contact invite accepted.",
@ -302,7 +302,7 @@
"contacts_confirmRemoveHistory": "Are you sure you want to permanently remove your chat history? Data cannot be restored", "contacts_confirmRemoveHistory": "Are you sure you want to permanently remove your chat history? Data cannot be restored",
"contacts_removeHistoryServerError": "There was an error while removing your chat history. Try again later", "contacts_removeHistoryServerError": "There was an error while removing your chat history. Try again later",
"contacts_fetchHistory": "Retrieve older history", "contacts_fetchHistory": "Retrieve older history",
"contacts_friends": "Friends", "contacts_friends": "Contacts",
"contacts_rooms": "Rooms", "contacts_rooms": "Rooms",
"contacts_leaveRoom": "Leave this room", "contacts_leaveRoom": "Leave this room",
"contacts_online": "Another user from this room is online", "contacts_online": "Another user from this room is online",
@ -689,7 +689,7 @@
"features_f_social": "Social applications", "features_f_social": "Social applications",
"features_f_social_note": "Create a profile, use an avatar, chat with contacts", "features_f_social_note": "Create a profile, use an avatar, chat with contacts",
"features_f_file1": "Upload and share files", "features_f_file1": "Upload and share files",
"features_f_file1_note": "Share files with your friends or embed them in your pads", "features_f_file1_note": "Share files with your contacts or embed them in your pads",
"features_f_storage1": "Permanent storage (50MB)", "features_f_storage1": "Permanent storage (50MB)",
"features_f_storage1_note": "Pads stored in your CryptDrive are never deleted for inactivity", "features_f_storage1_note": "Pads stored in your CryptDrive are never deleted for inactivity",
"features_f_register": "Register for free", "features_f_register": "Register for free",
@ -792,7 +792,7 @@
"a": "Registered users have access to a number of features unavailable to unregistered users. There's a chart <a href='/features.html' target='_blank'>here</a>." "a": "Registered users have access to a number of features unavailable to unregistered users. There's a chart <a href='/features.html' target='_blank'>here</a>."
}, },
"share": { "share": {
"q": "How can I share encrypted pads with my friends?", "q": "How can I share encrypted pads with my contacts?",
"a": "CryptPad puts the secret encryption key to your pad after the <em>#</em> character in the URL. Anything after this character is not sent to the server, so we never have access to your encryption keys. By sharing the link to a pad, you share the ability to read and access it." "a": "CryptPad puts the secret encryption key to your pad after the <em>#</em> character in the URL. Anything after this character is not sent to the server, so we never have access to your encryption keys. By sharing the link to a pad, you share the ability to read and access it."
}, },
"remove": { "remove": {
@ -824,7 +824,7 @@
"title": "Other questions", "title": "Other questions",
"pay": { "pay": {
"q": "Why should I pay when so many features are free?", "q": "Why should I pay when so many features are free?",
"a": "We give supporters additional storage and the ability to increase their friends' quotas (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>learn more</a>).<br><br>Beyond these short term benefits, by subscribing with a premium account you help to fund continued, active development of CryptPad. That includes fixing bugs, adding new features, and making it easier for others to help host CryptPad themselves. Additionally, you help to prove to other service providers that people are willing to support privacy enhancing technologies. It is our hope that eventually business models based on selling user data will become a thing of the past.<br><br>Finally, we offer most of CryptPad's functionality for free because we believe everyone deserves personal privacy, not just those with disposable income. By supporting us, you help us continue to make it possible for underprivileged populations to access these basic features without a price tag attached." "a": "We give supporters additional storage and the ability to increase their contacts' quotas (<a href='https://accounts.cryptpad.fr/#/faq' target='_blank'>learn more</a>).<br><br>Beyond these short term benefits, by subscribing with a premium account you help to fund continued, active development of CryptPad. That includes fixing bugs, adding new features, and making it easier for others to help host CryptPad themselves. Additionally, you help to prove to other service providers that people are willing to support privacy enhancing technologies. It is our hope that eventually business models based on selling user data will become a thing of the past.<br><br>Finally, we offer most of CryptPad's functionality for free because we believe everyone deserves personal privacy, not just those with disposable income. By supporting us, you help us continue to make it possible for underprivileged populations to access these basic features without a price tag attached."
}, },
"goal": { "goal": {
"q": "What is your goal?", "q": "What is your goal?",
@ -885,7 +885,7 @@
"colors": "Change the text and background colors using the <span class=\"fa fa-i-cursor\"></span> and <span class=\"fa fa-square\"></span> buttons" "colors": "Change the text and background colors using the <span class=\"fa fa-i-cursor\"></span> and <span class=\"fa fa-square\"></span> buttons"
}, },
"poll": { "poll": {
"decisions": "Make decisions in private among trusted friends", "decisions": "Make decisions in private among trusted contacts",
"options": "Propose options, and express your preferences", "options": "Propose options, and express your preferences",
"choices": "Click cells in your column to cycle through yes (<strong>✔</strong>), maybe (<strong>~</strong>), or no (<strong>✖</strong>)", "choices": "Click cells in your column to cycle through yes (<strong>✔</strong>), maybe (<strong>~</strong>), or no (<strong>✖</strong>)",
"submit": "Click <strong>submit</strong> to make your choices visible to others" "submit": "Click <strong>submit</strong> to make your choices visible to others"
@ -903,7 +903,7 @@
}, },
"driveReadmeTitle": "What is CryptPad?", "driveReadmeTitle": "What is CryptPad?",
"readme_welcome": "Welcome to CryptPad !", "readme_welcome": "Welcome to CryptPad !",
"readme_p1": "Welcome to CryptPad, this is where you can take note of things alone and with friends.", "readme_p1": "Welcome to CryptPad, this is where you can take note of things alone and with contacts.",
"readme_p2": "This pad will give you a quick walk through of how you can use CryptPad to take notes, keep them organized and work together on them.", "readme_p2": "This pad will give you a quick walk through of how you can use CryptPad to take notes, keep them organized and work together on them.",
"readme_cat1": "Get to know your CryptDrive", "readme_cat1": "Get to know your CryptDrive",
"readme_cat1_l1": "Make a pad: In your CryptDrive, click {0} then {1} and you can make a pad.", "readme_cat1_l1": "Make a pad: In your CryptDrive, click {0} then {1} and you can make a pad.",
@ -1075,18 +1075,19 @@
"friendRequest_later": "Decide later", "friendRequest_later": "Decide later",
"friendRequest_accept": "Accept (Enter)", "friendRequest_accept": "Accept (Enter)",
"friendRequest_decline": "Decline", "friendRequest_decline": "Decline",
"friendRequest_declined": "<b>{0}</b> declined your friend request", "friendRequest_declined": "<b>{0}</b> declined your contact request",
"friendRequest_accepted": "<b>{0}</b> accepted your friend request", "friendRequest_accepted": "<b>{0}</b> accepted your contact request",
"friendRequest_received": "<b>{0}</b> would like to be your friend", "friendRequest_received": "<b>{0}</b> would like to be your contact",
"friendRequest_notification": "<b>{0}</b> sent you a friend request", "friendRequest_notification": "<b>{0}</b> sent you a contact request",
"notifications_empty": "No notifications available", "notifications_empty": "No notifications available",
"notifications_title": "You have unread notifications", "notifications_title": "You have unread notifications",
"profile_addDescription": "Add a description", "profile_addDescription": "Add a description",
"profile_editDescription": "Edit your description", "profile_editDescription": "Edit your description",
"profile_addLink": "Add a link to your website", "profile_addLink": "Add a link to your website",
"profile_info": "Other users can find your profile through your avatar in document user lists.", "profile_info": "Other users can find your profile through your avatar in document user lists.",
"profile_friendRequestSent": "Friend request pending...", "profile_friendRequestSent": "Contact request pending...",
"profile_friend": "{0} is your friend", "isContact": "{0} is one of your contacts",
"isNotContact": "{0} is <b>not</b> one of your contacts",
"notification_padShared": "{0} has shared a pad with you: <b>{1}</b>", "notification_padShared": "{0} has shared a pad with you: <b>{1}</b>",
"notification_fileShared": "{0} has shared a file with you: <b>{1}</b>", "notification_fileShared": "{0} has shared a file with you: <b>{1}</b>",
"notification_folderShared": "{0} has shared a folder with you: <b>{1}</b>", "notification_folderShared": "{0} has shared a folder with you: <b>{1}</b>",
@ -1097,7 +1098,7 @@
"share_withFriends": "Share", "share_withFriends": "Share",
"notifications_dismiss": "Dismiss", "notifications_dismiss": "Dismiss",
"fm_info_sharedFolderHistory": "This is only the history of your shared folder: <b>{0}</b><br/>Your CryptDrive will stay in read-only mode while you navigate.", "fm_info_sharedFolderHistory": "This is only the history of your shared folder: <b>{0}</b><br/>Your CryptDrive will stay in read-only mode while you navigate.",
"share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends.", "share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad contacts.",
"supportPage": "Support", "supportPage": "Support",
"admin_cat_support": "Support", "admin_cat_support": "Support",
"admin_supportInitHelp": "Your server is not yet configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in \"./scripts/generate-admin-keys.js\", then store the public key in the \"config.js\" file and send you the private key.", "admin_supportInitHelp": "Your server is not yet configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in \"./scripts/generate-admin-keys.js\", then store the public key in the \"config.js\" file and send you the private key.",
@ -1130,7 +1131,7 @@
"notificationsPage": "Notifications", "notificationsPage": "Notifications",
"openNotificationsApp": "Open notifications panel", "openNotificationsApp": "Open notifications panel",
"notifications_cat_all": "All", "notifications_cat_all": "All",
"notifications_cat_friends": "Friend requests", "notifications_cat_friends": "Contact requests",
"notifications_cat_pads": "Shared with me", "notifications_cat_pads": "Shared with me",
"notifications_cat_archived": "History", "notifications_cat_archived": "History",
"notifications_dismissAll": "Dismiss all", "notifications_dismissAll": "Dismiss all",
@ -1138,8 +1139,6 @@
"requestEdit_button": "Request edit rights", "requestEdit_button": "Request edit rights",
"requestEdit_dialog": "Are you sure you'd like to ask the owner of this pad for the ability to edit?", "requestEdit_dialog": "Are you sure you'd like to ask the owner of this pad for the ability to edit?",
"requestEdit_confirm": "{1} has asked for the ability to edit the pad <b>{0}</b>. Would you like to grant them access?", "requestEdit_confirm": "{1} has asked for the ability to edit the pad <b>{0}</b>. Would you like to grant them access?",
"requestEdit_fromFriend": "You are friends with {0}",
"requestEdit_fromStranger": "You are <b>not</b> friends with {0}",
"requestEdit_viewPad": "Open the pad in a new tab", "requestEdit_viewPad": "Open the pad in a new tab",
"later": "Decide later", "later": "Decide later",
"requestEdit_request": "{1} wants to edit the pad <b>{0}</b>", "requestEdit_request": "{1} wants to edit the pad <b>{0}</b>",
@ -1153,7 +1152,7 @@
"features_emailRequired": "Email address required", "features_emailRequired": "Email address required",
"owner_removeText": "Remove an existing owner", "owner_removeText": "Remove an existing owner",
"owner_removePendingText": "Cancel a pending offer", "owner_removePendingText": "Cancel a pending offer",
"owner_addText": "Offer co-ownership to a friend", "owner_addText": "Offer co-ownership to a contact",
"owner_unknownUser": "Unknown user", "owner_unknownUser": "Unknown user",
"owner_removeButton": "Remove selected owners", "owner_removeButton": "Remove selected owners",
"owner_removePendingButton": "Cancel selected offers", "owner_removePendingButton": "Cancel selected offers",
@ -1169,9 +1168,8 @@
"owner_removed": "{0} has removed your ownership of <b>{1}</b>", "owner_removed": "{0} has removed your ownership of <b>{1}</b>",
"owner_removedPending": "{0} has canceled your ownership offer for <b>{1}</b>", "owner_removedPending": "{0} has canceled your ownership offer for <b>{1}</b>",
"share_linkTeam": "Add to team drive", "share_linkTeam": "Add to team drive",
"team_pickFriends": "Choose which friends to invite to this team", "team_pickFriends": "Choose which contacts to invite to this team",
"team_inviteModalButton": "Invite", "team_inviteModalButton": "Invite",
"team_noFriend": "You haven't connected with any friends on CryptPad yet.",
"team_pcsSelectLabel": "Store in", "team_pcsSelectLabel": "Store in",
"team_pcsSelectHelp": "Creating an owned pad in your team's drive will give ownership to the team.", "team_pcsSelectHelp": "Creating an owned pad in your team's drive will give ownership to the team.",
"team_invitedToTeam": "{0} has invited you to join their team: <b>{1}</b>", "team_invitedToTeam": "{0} has invited you to join their team: <b>{1}</b>",
@ -1193,7 +1191,7 @@
"team_rosterPromote": "Promote", "team_rosterPromote": "Promote",
"team_rosterDemote": "Demote", "team_rosterDemote": "Demote",
"team_rosterKick": "Kick from the team", "team_rosterKick": "Kick from the team",
"team_inviteButton": "Invite friends", "team_inviteButton": "Invite contacts",
"team_leaveButton": "Leave this team", "team_leaveButton": "Leave this team",
"team_leaveConfirm": "If you leave this team you will lose access to its CryptDrive, chat history, and other contents. Are you sure?", "team_leaveConfirm": "If you leave this team you will lose access to its CryptDrive, chat history, and other contents. Are you sure?",
"team_owner": "Owners", "team_owner": "Owners",
@ -1294,5 +1292,6 @@
"oo_exportInProgress": "Export in progress", "oo_exportInProgress": "Export in progress",
"oo_sheetMigration_loading": "Upgrading your spreadsheet to the latest version", "oo_sheetMigration_loading": "Upgrading your spreadsheet to the latest version",
"oo_sheetMigration_complete": "Updated version available, press OK to reload.", "oo_sheetMigration_complete": "Updated version available, press OK to reload.",
"oo_sheetMigration_anonymousEditor": "Editing this spreadsheet is disabled for anonymous users until it is upgraded to the latest version by a registered user." "oo_sheetMigration_anonymousEditor": "Editing this spreadsheet is disabled for anonymous users until it is upgraded to the latest version by a registered user.",
"imprint": "Legal notice"
} }

@ -9,6 +9,7 @@ define([
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
var hash, href;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -19,6 +20,14 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -37,19 +46,25 @@ define([
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var afterSecrets = function (Cryptpad, Utils, secret, cb) { var afterSecrets = function (Cryptpad, Utils, secret, cb) {
var hash = window.location.hash.slice(1); var _hash = hash.slice(1);
if (hash && Utils.LocalStore.isLoggedIn()) { if (_hash && Utils.LocalStore.isLoggedIn()) {
// Add a shared folder! // Add a shared folder!
Cryptpad.addSharedFolder(null, secret, function (id) { Cryptpad.addSharedFolder(null, secret, function (id) {
window.CryptPad_newSharedFolder = id; window.CryptPad_newSharedFolder = id;
// Clear the hash now that the secrets have been generated
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
cb(); cb();
}); });
return; return;
} else if (hash) { } else if (_hash) {
var id = Utils.Util.createRandomInteger(); var id = Utils.Util.createRandomInteger();
window.CryptPad_newSharedFolder = id; window.CryptPad_newSharedFolder = id;
var data = { var data = {
href: Utils.Hash.getRelativeHref(window.location.href), href: Utils.Hash.getRelativeHref(Cryptpad.currentPad.href),
password: secret.password password: secret.password
}; };
return void Cryptpad.loadSharedFolder(id, data, cb); return void Cryptpad.loadSharedFolder(id, data, cb);
@ -82,14 +97,17 @@ define([
cb(obj); cb(obj);
}); });
}); });
sframeChan.on('EV_DRIVE_SET_HASH', function (hash) { sframeChan.on('EV_DRIVE_SET_HASH', function (/*hash*/) {
// Update the hash in the address bar // Update the hash in the address bar
// XXX Hidden hash: don't put the shared folder href in the address bar
/*
if (!Utils.LocalStore.isLoggedIn()) { return; } if (!Utils.LocalStore.isLoggedIn()) { return; }
var ohc = window.onhashchange; var ohc = window.onhashchange;
window.onhashchange = function () {}; window.onhashchange = function () {};
window.location.hash = hash || ''; window.location.hash = hash || '';
window.onhashchange = ohc; window.onhashchange = ohc;
ohc({reset:true}); ohc({reset:true});
*/
}); });
Cryptpad.onNetworkDisconnect.reg(function () { Cryptpad.onNetworkDisconnect.reg(function () {
sframeChan.event('EV_NETWORK_DISCONNECT'); sframeChan.event('EV_NETWORK_DISCONNECT');
@ -107,11 +125,13 @@ define([
sframeChan.event('EV_DRIVE_REMOVE', data); sframeChan.event('EV_DRIVE_REMOVE', data);
}); });
}; };
var addData = function (meta) { var addData = function (meta, Cryptpad) {
if (!window.CryptPad_newSharedFolder) { return; } if (!window.CryptPad_newSharedFolder) { return; }
meta.anonSFHref = window.location.href; meta.anonSFHref = Cryptpad.currentPad.href;
}; };
SFCommonO.start({ SFCommonO.start({
hash: hash,
href: href,
afterSecrets: afterSecrets, afterSecrets: afterSecrets,
noHash: true, noHash: true,
noRealtime: true, noRealtime: true,

@ -9,6 +9,7 @@ define([
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
var hash, href;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -19,6 +20,14 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -36,10 +45,12 @@ define([
}; };
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var addData = function (meta) { var addData = function (meta, Cryptpad) {
meta.filehash = window.location.hash; meta.filehash = Cryptpad.currentPad.hash;
}; };
SFCommonO.start({ SFCommonO.start({
hash: hash,
href: href,
noRealtime: true, noRealtime: true,
addData: addData addData: addData
}); });

@ -9,6 +9,7 @@ define([
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
var hash, href;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -19,6 +20,14 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -37,6 +46,8 @@ define([
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
SFCommonO.start({ SFCommonO.start({
hash: hash,
href: href,
useCreationScreen: true, useCreationScreen: true,
messaging: true messaging: true
}); });

@ -201,7 +201,7 @@ define([
// Add friend message // Add friend message
APP.$friend.append(h('p.cp-app-profile-friend', [ APP.$friend.append(h('p.cp-app-profile-friend', [
h('i.fa.fa-address-book'), h('i.fa.fa-address-book'),
Messages._getKey('profile_friend', [name]) Messages._getKey('isContact', [name])
])); ]));
if (!friends[data.curvePublic].notifications) { return; } if (!friends[data.curvePublic].notifications) { return; }
// Add unfriend button // Add unfriend button

@ -52,14 +52,16 @@ define([
'cp-settings-displayname', 'cp-settings-displayname',
'cp-settings-language-selector', 'cp-settings-language-selector',
'cp-settings-resettips', 'cp-settings-resettips',
'cp-settings-logout-everywhere',
'cp-settings-autostore',
'cp-settings-userfeedback',
'cp-settings-change-password', 'cp-settings-change-password',
'cp-settings-migrate', 'cp-settings-migrate',
'cp-settings-backup',
'cp-settings-delete' 'cp-settings-delete'
], ],
'security': [ // XXX
'cp-settings-logout-everywhere',
'cp-settings-autostore',
'cp-settings-safe-links',
'cp-settings-userfeedback',
],
'creation': [ 'creation': [
'cp-settings-creation-owned', 'cp-settings-creation-owned',
'cp-settings-creation-expire', 'cp-settings-creation-expire',
@ -115,6 +117,24 @@ define([
var create = {}; var create = {};
var makeBlock = function (key, getter, full) {
var safeKey = key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
create[key] = function () {
var $div = $('<div>', {'class': 'cp-settings-' + key + ' cp-sidebarlayout-element'});
if (full) {
$('<label>').text(Messages['settings_'+safeKey+'Title'] || key).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages['settings_'+safeKey+'Hint'] || 'Coming soon...').appendTo($div);
}
getter(function (content) {
$div.append(content);
}, $div);
return $div;
};
};
// Account settings // Account settings
create['info-block'] = function () { create['info-block'] = function () {
@ -547,6 +567,35 @@ define([
return $div; return $div;
}; };
// Security
makeBlock('safe-links', function (cb) {
// XXX settings_safeLinksTitle, settings_safeLinksHint, settings_safeLinksCheckbox
var $cbox = $(UI.createCheckbox('cp-settings-safe-links',
Messages.settings_safeLinksCheckbox,
true, { label: {class: 'noTitle'} }));
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function () {
spinner.spin();
var val = !$checkbox.is(':checked');
common.setAttribute(['security', 'unsafeLinks'], val, function () {
spinner.done();
});
});
common.getAttribute(['security', 'unsafeLinks'], function (e, val) {
if (e) { return void console.error(e); }
if (!val) {
$checkbox.attr('checked', 'checked');
}
});
cb($cbox);
}, true);
// Pad Creation settings // Pad Creation settings
var setHTML = function (e, html) { var setHTML = function (e, html) {
@ -1578,6 +1627,7 @@ define([
if (key === 'code') { $category.append($('<span>', {'class': 'fa fa-file-code-o' })); } if (key === 'code') { $category.append($('<span>', {'class': 'fa fa-file-code-o' })); }
if (key === 'pad') { $category.append($('<span>', {'class': 'fa fa-file-word-o' })); } if (key === 'pad') { $category.append($('<span>', {'class': 'fa fa-file-word-o' })); }
if (key === 'creation') { $category.append($('<span>', {'class': 'fa fa-plus-circle' })); } if (key === 'creation') { $category.append($('<span>', {'class': 'fa fa-plus-circle' })); }
if (key === 'security') { $category.append($('<span>', {'class': 'fa fa-lock' })); }
if (key === 'subscription') { $category.append($('<span>', {'class': 'fa fa-star-o' })); } if (key === 'subscription') { $category.append($('<span>', {'class': 'fa fa-star-o' })); }
if (key === active) { if (key === active) {
@ -1596,9 +1646,10 @@ define([
showCategories(categories[key]); showCategories(categories[key]);
}); });
$category.append(Messages['settings_cat_'+key]); $category.append(Messages['settings_cat_'+key] || key);
}); });
showCategories(categories[active]); showCategories(categories[active]);
common.setHash(active);
}; };

@ -170,7 +170,9 @@ define([
var privateData = metadataMgr.getPrivateData(); var privateData = metadataMgr.getPrivateData();
// Check content.sender to see if it comes from us or from an admin // Check content.sender to see if it comes from us or from an admin
var fromMe = content.sender && content.sender.edPublic === privateData.edPublic; var senderKey = content.sender && content.sender.edPublic;
var fromMe = senderKey === privateData.edPublic;
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1;
var userData = h('div.cp-support-showdata', [ var userData = h('div.cp-support-showdata', [
Messages.support_showData, Messages.support_showData,
@ -183,7 +185,7 @@ define([
}); });
var name = Util.fixHTML(content.sender.name) || Messages.anonymous; var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
return h('div.cp-support-list-message', { return h('div.cp-support-list-message' + (fromAdmin? '.cp-support-fromadmin': ''), {
'data-hash': hash 'data-hash': hash
}, [ }, [
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [ h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [
@ -219,6 +221,7 @@ define([
common: common, common: common,
isAdmin: isAdmin, isAdmin: isAdmin,
pinUsage: pinUsage || false, pinUsage: pinUsage || false,
adminKeys: Array.isArray(ApiConfig.adminKeys)? ApiConfig.adminKeys.slice(): [],
}; };
ui.sendForm = function (id, form, dest) { ui.sendForm = function (id, form, dest) {

@ -9,6 +9,7 @@ define([
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
var hash, href;
nThen(function (waitFor) { nThen(function (waitFor) {
DomReady.onReady(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -19,6 +20,14 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
// Hidden hash
hash = window.location.hash;
href = window.location.href;
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
document.getElementById('sbox-iframe').setAttribute('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/teams/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/teams/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -37,7 +46,6 @@ define([
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var teamId; var teamId;
var hash = window.location.hash.slice(1);
var addRpc = function (sframeChan, Cryptpad) { var addRpc = function (sframeChan, Cryptpad) {
sframeChan.on('Q_SET_TEAM', function (data, cb) { sframeChan.on('Q_SET_TEAM', function (data, cb) {
teamId = data; teamId = data;
@ -95,7 +103,7 @@ define([
}; };
var addData = function (meta) { var addData = function (meta) {
if (!hash) { return; } if (!hash) { return; }
meta.teamInviteHash = hash; meta.teamInviteHash = hash.slice(1);
}; };
SFCommonO.start({ SFCommonO.start({
getSecrets: getSecrets, getSecrets: getSecrets,

Loading…
Cancel
Save