Merge branch 'staging' into mute
This commit is contained in:
@@ -127,6 +127,18 @@ define([
|
||||
return input;
|
||||
};
|
||||
|
||||
dialog.selectableArea = function (value, opt) {
|
||||
var attrs = merge({
|
||||
readonly: 'readonly',
|
||||
}, opt);
|
||||
|
||||
var input = h('textarea', attrs);
|
||||
$(input).val(value).click(function () {
|
||||
input.select();
|
||||
});
|
||||
return input;
|
||||
};
|
||||
|
||||
dialog.okButton = function (content, classString) {
|
||||
var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary';
|
||||
return h(sel, { tabindex: '2', }, content || Messages.okButton);
|
||||
@@ -463,7 +475,7 @@ define([
|
||||
opt = opt || {};
|
||||
|
||||
var inputBlock = opt.password ? UI.passwordInput() : dialog.textInput();
|
||||
var input = opt.password ? $(inputBlock).find('input')[0] : inputBlock;
|
||||
var input = $(inputBlock).is('input') ? inputBlock : $(inputBlock).find('input')[0];
|
||||
input.value = typeof(def) === 'string'? def: '';
|
||||
|
||||
var message;
|
||||
|
||||
@@ -147,6 +147,7 @@ define([
|
||||
: Messages.owner_removeText;
|
||||
var removeCol = UIElements.getUserGrid(msg, {
|
||||
common: common,
|
||||
large: true,
|
||||
data: _owners,
|
||||
noFilter: true
|
||||
}, function () {
|
||||
@@ -238,6 +239,7 @@ define([
|
||||
});
|
||||
var addCol = UIElements.getUserGrid(Messages.owner_addText, {
|
||||
common: common,
|
||||
large: true,
|
||||
data: _friends
|
||||
}, function () {
|
||||
//console.log(arguments);
|
||||
@@ -254,6 +256,7 @@ define([
|
||||
});
|
||||
var teamsList = UIElements.getUserGrid(Messages.owner_addTeamText, {
|
||||
common: common,
|
||||
large: true,
|
||||
noFilter: true,
|
||||
data: teamsData
|
||||
}, function () {});
|
||||
@@ -551,9 +554,10 @@ define([
|
||||
$d.append(password);
|
||||
}
|
||||
|
||||
if (!data.noEditPassword && owned && parsed.type !== "sheet") { // FIXME SHEET fix password change for sheets
|
||||
if (!data.noEditPassword && owned) { // FIXME SHEET fix password change for sheets
|
||||
var sframeChan = common.getSframeChannel();
|
||||
|
||||
var isOO = parsed.type === 'sheet';
|
||||
var isFile = parsed.hashData.type === 'file';
|
||||
var isSharedFolder = parsed.type === 'drive';
|
||||
|
||||
@@ -586,7 +590,8 @@ define([
|
||||
UI.confirm(changePwConfirm, function (yes) {
|
||||
if (!yes) { pLocked = false; return; }
|
||||
$(passwordOk).html('').append(h('span.fa.fa-spinner.fa-spin', {style: 'margin-left: 0'}));
|
||||
var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE';
|
||||
var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' :
|
||||
(isOO ? 'Q_OO_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE');
|
||||
|
||||
// If this is a file password change, register to the upload events:
|
||||
// * if there is a pending upload, ask if we shoudl interrupt
|
||||
@@ -737,12 +742,22 @@ define([
|
||||
UIElements.getProperties = function (common, data, cb) {
|
||||
var c1;
|
||||
var c2;
|
||||
var button = [{
|
||||
className: 'primary',
|
||||
name: Messages.okButton,
|
||||
onClick: function () {},
|
||||
keys: [13]
|
||||
}];
|
||||
NThen(function (waitFor) {
|
||||
getPadProperties(common, data, waitFor(function (e, c) {
|
||||
c1 = c[0];
|
||||
c1 = UI.dialog.customModal(c[0], {
|
||||
buttons: button
|
||||
});
|
||||
}));
|
||||
getRightsProperties(common, data, waitFor(function (e, c) {
|
||||
c2 = c[0];
|
||||
c2 = UI.dialog.customModal(c[0], {
|
||||
buttons: button
|
||||
});
|
||||
}));
|
||||
}).nThen(function () {
|
||||
var tabs = UI.dialog.tabs([{
|
||||
@@ -782,8 +797,6 @@ define([
|
||||
|
||||
var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : '';
|
||||
|
||||
var buttonSelect = h('button', Messages.share_selectAll);
|
||||
var buttonDeselect = h('button', Messages.share_deselectAll);
|
||||
var inputFilter = h('input', {
|
||||
placeholder: Messages.share_filterFriend
|
||||
});
|
||||
@@ -791,9 +804,7 @@ define([
|
||||
var div = h('div.cp-usergrid-container' + noOthers + (config.large?'.large':''), [
|
||||
label ? h('label', label) : undefined,
|
||||
h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [
|
||||
inputFilter,
|
||||
buttonSelect,
|
||||
buttonDeselect
|
||||
inputFilter
|
||||
]),
|
||||
]);
|
||||
var $div = $(div);
|
||||
@@ -806,23 +817,8 @@ define([
|
||||
$div.find('.cp-usergrid-user:not(.cp-selected):not([data-name*="'+name+'"])').hide();
|
||||
}
|
||||
};
|
||||
|
||||
$(inputFilter).on('keydown keyup change', redraw);
|
||||
|
||||
$(buttonSelect).click(function () {
|
||||
$div.find('.cp-usergrid-user:not(.cp-selected):visible').addClass('cp-selected');
|
||||
onSelect();
|
||||
});
|
||||
$(buttonDeselect).click(function () {
|
||||
$div.find('.cp-usergrid-user.cp-selected').removeClass('cp-selected').each(function (i, el) {
|
||||
var order = $(el).attr('data-order');
|
||||
if (!order) { return; }
|
||||
$(el).attr('style', 'order:'+order);
|
||||
});
|
||||
redraw();
|
||||
onSelect();
|
||||
});
|
||||
|
||||
$(div).append(h('div.cp-usergrid-grid', icons));
|
||||
if (!config.noSelect) {
|
||||
$div.on('click', '.cp-usergrid-user', function () {
|
||||
@@ -880,10 +876,11 @@ define([
|
||||
delete friends[curve];
|
||||
});
|
||||
|
||||
var friendsList = UIElements.getUserGrid(null, {
|
||||
var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, {
|
||||
common: common,
|
||||
data: friends,
|
||||
noFilter: false
|
||||
noFilter: false,
|
||||
large: true
|
||||
}, refreshButtons);
|
||||
var friendDiv = friendsList.div;
|
||||
$div.append(friendDiv);
|
||||
@@ -909,6 +906,7 @@ define([
|
||||
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
|
||||
common: common,
|
||||
noFilter: true,
|
||||
large: true,
|
||||
data: teams
|
||||
}, refreshButtons);
|
||||
$div.append(teamsList.div);
|
||||
@@ -1002,10 +1000,53 @@ define([
|
||||
});
|
||||
return {
|
||||
content: div,
|
||||
button: shareButton
|
||||
buttons: [shareButton]
|
||||
};
|
||||
};
|
||||
|
||||
var noContactsMessage = function(common){
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var data = metadataMgr.getUserData();
|
||||
var origin = metadataMgr.getPrivateData().origin;
|
||||
if (common.isLoggedIn()) {
|
||||
return {
|
||||
content: h('p', Messages.share_noContactsLoggedIn),
|
||||
buttons: [{
|
||||
className: 'secondary',
|
||||
name: Messages.share_copyProfileLink,
|
||||
onClick: function () {
|
||||
var profile = data.profile ? (origin + '/profile/#' + data.profile) : '';
|
||||
var success = Clipboard.copy(profile);
|
||||
if (success) { UI.log(Messages.shareSuccess); }
|
||||
},
|
||||
keys: [13]
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: h('p', Messages.share_noContactsNotLoggedIn),
|
||||
buttons: [{
|
||||
className: 'secondary',
|
||||
name: Messages.login_register,
|
||||
onClick: function () {
|
||||
common.setLoginRedirect(function () {
|
||||
common.gotoURL('/register/');
|
||||
});
|
||||
}
|
||||
}, {
|
||||
className: 'secondary',
|
||||
name: Messages.login_login,
|
||||
onClick: function () {
|
||||
common.setLoginRedirect(function () {
|
||||
common.gotoURL('/login/');
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
UIElements.createShareModal = function (config) {
|
||||
var origin = config.origin;
|
||||
var pathname = config.pathname;
|
||||
@@ -1014,12 +1055,29 @@ define([
|
||||
|
||||
if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }
|
||||
|
||||
// check if the pad is password protected
|
||||
var hash = hashes.editHash || hashes.viewHash;
|
||||
var href = origin + pathname + '#' + hash;
|
||||
var parsedHref = Hash.parsePadUrl(href);
|
||||
var hasPassword = parsedHref.hashData.password;
|
||||
|
||||
var makeFaqLink = function () {
|
||||
var link = h('span', [
|
||||
h('i.fa.fa-question-circle'),
|
||||
h('a', {href: '#'}, Messages.passwordFaqLink)
|
||||
]);
|
||||
$(link).click(function () {
|
||||
common.openURL(config.origin + "/faq.html#security-pad_password");
|
||||
});
|
||||
return link;
|
||||
};
|
||||
|
||||
|
||||
var parsed = Hash.parsePadUrl(pathname);
|
||||
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
|
||||
|
||||
var rights = h('div.msg.cp-inline-radio-group', [
|
||||
h('label', Messages.share_linkAccess),
|
||||
h('br'),
|
||||
h('div.radio-group',[
|
||||
UI.createRadio('accessRights', 'cp-share-editable-false',
|
||||
Messages.share_linkView, true, { mark: {tabindex:1} }),
|
||||
@@ -1066,9 +1124,42 @@ define([
|
||||
h('br'),
|
||||
] : [
|
||||
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
|
||||
h('br'),
|
||||
];
|
||||
linkContent.push(UI.dialog.selectable('', { id: 'cp-share-link-preview', tabindex: 1 }));
|
||||
linkContent.push(h('div.cp-spacer'));
|
||||
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));
|
||||
|
||||
// Show alert if the pad is password protected
|
||||
if (hasPassword) {
|
||||
linkContent.push(h('div.alert.alert-primary', [
|
||||
h('i.fa.fa-lock'),
|
||||
Messages.share_linkPasswordAlert, h('br'),
|
||||
makeFaqLink()
|
||||
]));
|
||||
}
|
||||
|
||||
// warning about sharing links
|
||||
var localStore = window.cryptpadStore;
|
||||
var dismissButton = h('span.fa.fa-times');
|
||||
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
|
||||
{ style: 'display: none;' },
|
||||
[
|
||||
h('span.cp-inline-alert-text', Messages.share_linkWarning),
|
||||
dismissButton
|
||||
]);
|
||||
linkContent.push(shareLinkWarning);
|
||||
|
||||
localStore.get('hide-alert-shareLinkWarning', function (val) {
|
||||
if (val === '1') { return; }
|
||||
$(shareLinkWarning).show();
|
||||
|
||||
$(dismissButton).on('click', function () {
|
||||
localStore.put('hide-alert-shareLinkWarning', '1');
|
||||
$(shareLinkWarning).remove();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
var link = h('div.cp-share-modal', linkContent);
|
||||
var $link = $(link);
|
||||
@@ -1125,21 +1216,32 @@ define([
|
||||
|
||||
var hasFriends = Object.keys(config.friends || {}).length !== 0;
|
||||
var onFriendShare = Util.mkEvent();
|
||||
var friendsObject = hasFriends ? createShareWithFriends(config, onFriendShare, getLinkValue) : {
|
||||
content: h('p', Messages.team_noFriend),
|
||||
button: {}
|
||||
};
|
||||
|
||||
|
||||
var friendsObject = hasFriends ? createShareWithFriends(config, onFriendShare, getLinkValue) : noContactsMessage(common);
|
||||
var friendsList = friendsObject.content;
|
||||
|
||||
onFriendShare.reg(saveValue);
|
||||
|
||||
// XXX Don't display access rights if no contacts
|
||||
var contactsContent = h('div.cp-share-modal');
|
||||
$(contactsContent).append(friendsList);
|
||||
var $contactsContent = $(contactsContent);
|
||||
|
||||
$contactsContent.append(friendsList);
|
||||
|
||||
// Show alert if the pad is password protected
|
||||
if (hasPassword) {
|
||||
$contactsContent.append(h('div.alert.alert-primary', [
|
||||
h('i.fa.fa-unlock'),
|
||||
Messages.share_contactPasswordAlert, h('br'),
|
||||
makeFaqLink()
|
||||
]));
|
||||
}
|
||||
|
||||
var contactButtons = [makeCancelButton(),
|
||||
friendsObject.button];
|
||||
|
||||
var contactButtons = friendsObject.buttons;
|
||||
contactButtons.unshift(makeCancelButton());
|
||||
|
||||
var frameContacts = UI.dialog.customModal(contactsContent, {
|
||||
buttons: contactButtons,
|
||||
onClose: config.onClose,
|
||||
@@ -1154,9 +1256,18 @@ define([
|
||||
};
|
||||
var embedContent = [
|
||||
h('p', Messages.viewEmbedTag),
|
||||
h('br'),
|
||||
UI.dialog.selectable(getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1 })
|
||||
UI.dialog.selectableArea(getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1, rows: 3})
|
||||
];
|
||||
|
||||
// Show alert if the pad is password protected
|
||||
if (hasPassword) {
|
||||
embedContent.push(h('div.alert.alert-primary', [
|
||||
h('i.fa.fa-lock'), ' ',
|
||||
Messages.share_embedPasswordAlert, h('br'),
|
||||
makeFaqLink()
|
||||
]));
|
||||
}
|
||||
|
||||
var embedButtons = [
|
||||
makeCancelButton(), {
|
||||
className: 'primary',
|
||||
@@ -1261,6 +1372,21 @@ define([
|
||||
if (!hashes.fileHash) { throw new Error("You must provide a file hash"); }
|
||||
var url = origin + pathname + '#' + hashes.fileHash;
|
||||
|
||||
// check if the file is password protected
|
||||
var parsedHref = Hash.parsePadUrl(url);
|
||||
var hasPassword = parsedHref.hashData.password;
|
||||
|
||||
var makeFaqLink = function () {
|
||||
var link = h('span', [
|
||||
h('i.fa.fa-question-circle'),
|
||||
h('a', {href: '#'}, Messages.passwordFaqLink)
|
||||
]);
|
||||
$(link).click(function () {
|
||||
common.openURL(config.origin + "/faq.html#security-pad_password");
|
||||
});
|
||||
return link;
|
||||
};
|
||||
|
||||
var getLinkValue = function () { return url; };
|
||||
|
||||
var makeCancelButton = function() {
|
||||
@@ -1272,9 +1398,40 @@ define([
|
||||
|
||||
// Share link tab
|
||||
var linkContent = [
|
||||
UI.dialog.selectable(getLinkValue(), { id: 'cp-share-link-preview', tabindex: 1 })
|
||||
UI.dialog.selectableArea(getLinkValue(), { id: 'cp-share-link-preview', tabindex: 1, rows:2 })
|
||||
];
|
||||
|
||||
// Show alert if the pad is password protected
|
||||
if (hasPassword) {
|
||||
linkContent.push(h('div.alert.alert-primary', [
|
||||
h('i.fa.fa-lock'),
|
||||
Messages.share_linkPasswordAlert, h('br'),
|
||||
makeFaqLink()
|
||||
]));
|
||||
}
|
||||
|
||||
// warning about sharing links
|
||||
var localStore = window.cryptpadStore;
|
||||
var dismissButton = h('span.fa.fa-times');
|
||||
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
|
||||
{ style: 'display: none;' },
|
||||
[
|
||||
h('span.cp-inline-alert-text', Messages.share_linkWarning),
|
||||
dismissButton
|
||||
]);
|
||||
linkContent.push(shareLinkWarning);
|
||||
|
||||
localStore.get('hide-alert-shareLinkWarning', function (val) {
|
||||
if (val === '1') { return; }
|
||||
$(shareLinkWarning).show();
|
||||
|
||||
$(dismissButton).on('click', function () {
|
||||
localStore.put('hide-alert-shareLinkWarning', '1');
|
||||
$(shareLinkWarning).remove();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var link = h('div.cp-share-modal', linkContent);
|
||||
|
||||
var linkButtons = [
|
||||
@@ -1300,17 +1457,24 @@ define([
|
||||
// share with contacts tab
|
||||
var hasFriends = Object.keys(config.friends || {}).length !== 0;
|
||||
|
||||
var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : {
|
||||
content: h('p', Messages.share_noContacts),
|
||||
button: {}
|
||||
};
|
||||
var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : noContactsMessage(common);
|
||||
var friendsList = friendsObject.content;
|
||||
|
||||
var contactsContent = h('div.cp-share-modal');
|
||||
$(contactsContent).append(friendsList);
|
||||
var $contactsContent = $(contactsContent);
|
||||
$contactsContent.append(friendsList);
|
||||
|
||||
var contactButtons = [makeCancelButton(),
|
||||
friendsObject.button];
|
||||
// Show alert if the pad is password protected
|
||||
if (hasPassword) {
|
||||
$contactsContent.append(h('div.alert.alert-primary', [
|
||||
h('i.fa.fa-unlock'),
|
||||
Messages.share_contactPasswordAlert, h('br'),
|
||||
makeFaqLink()
|
||||
]));
|
||||
}
|
||||
|
||||
var contactButtons = friendsObject.buttons;
|
||||
contactButtons.unshift(makeCancelButton());
|
||||
|
||||
var frameContacts = UI.dialog.customModal(contactsContent, {
|
||||
buttons: contactButtons,
|
||||
@@ -1321,12 +1485,20 @@ define([
|
||||
// Embed tab
|
||||
var embed = h('div.cp-share-modal', [
|
||||
h('p', Messages.fileEmbedScript),
|
||||
h('br'),
|
||||
UI.dialog.selectable(common.getMediatagScript()),
|
||||
h('p', Messages.fileEmbedTag),
|
||||
h('br'),
|
||||
UI.dialog.selectable(common.getMediatagFromHref(fileData)),
|
||||
]);
|
||||
|
||||
// Show alert if the pad is password protected
|
||||
if (hasPassword) {
|
||||
embed.append(h('div.alert.alert-primary', [
|
||||
h('i.fa.fa-lock'), ' ',
|
||||
Messages.share_embedPasswordAlert, h('br'),
|
||||
makeFaqLink()
|
||||
]));
|
||||
}
|
||||
|
||||
var embedButtons = [{
|
||||
className: 'cancel',
|
||||
name: Messages.cancel,
|
||||
@@ -1403,6 +1575,7 @@ define([
|
||||
var list = UIElements.getUserGrid(Messages.team_pickFriends, {
|
||||
common: common,
|
||||
data: config.friends,
|
||||
large: true
|
||||
}, refreshButton);
|
||||
$div = $(list.div);
|
||||
refreshButton();
|
||||
@@ -1761,7 +1934,7 @@ define([
|
||||
if (e) { return void console.error(e); }
|
||||
UIElements.getProperties(common, data, function (e, $prop) {
|
||||
if (e) { return void console.error(e); }
|
||||
UI.alert($prop[0], undefined, true);
|
||||
UI.openCustomModal($prop[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1155,6 +1155,242 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
common.changeOOPassword = function (data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var href = data.href;
|
||||
var newPassword = data.password;
|
||||
var teamId = data.teamId;
|
||||
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
if (parsed.type !== 'sheet') { return void cb({ error: 'EINVAL_TYPE' }); }
|
||||
|
||||
var warning = false;
|
||||
var newHash, newRoHref;
|
||||
var oldSecret;
|
||||
var oldMetadata;
|
||||
var oldRtChannel;
|
||||
var privateData;
|
||||
var padData;
|
||||
|
||||
var newSecret;
|
||||
if (parsed.hashData.version >= 2) {
|
||||
newSecret = Hash.getSecrets(parsed.type, parsed.hash, newPassword);
|
||||
if (!(newSecret.keys && newSecret.keys.editKeyStr)) {
|
||||
return void cb({error: 'EAUTH'});
|
||||
}
|
||||
newHash = Hash.getEditHashFromKeys(newSecret);
|
||||
}
|
||||
var newHref = '/' + parsed.type + '/#' + newHash;
|
||||
var newRtChannel = Hash.createChannelId();
|
||||
|
||||
var Crypt, Crypto;
|
||||
var cryptgetVal;
|
||||
var optsPut = {
|
||||
password: newPassword,
|
||||
metadata: {
|
||||
validateKey: newSecret.keys.validateKey
|
||||
},
|
||||
};
|
||||
|
||||
Nthen(function (waitFor) {
|
||||
common.getPadAttribute('', waitFor(function (err, _data) {
|
||||
padData = _data;
|
||||
}), href);
|
||||
}).nThen(function (waitFor) {
|
||||
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password);
|
||||
|
||||
require([
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
], waitFor(function (_Crypt, _Crypto) {
|
||||
Crypt = _Crypt;
|
||||
Crypto = _Crypto;
|
||||
}));
|
||||
|
||||
common.getPadMetadata({channel: oldSecret.channel}, waitFor(function (metadata) {
|
||||
oldMetadata = metadata;
|
||||
}));
|
||||
common.getMetadata(waitFor(function (err, data) {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({ error: err });
|
||||
}
|
||||
privateData = data.priv;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
// Check if we're allowed to change the password
|
||||
var owners = oldMetadata.owners;
|
||||
optsPut.metadata.owners = owners;
|
||||
var edPublic = teamId ? (privateData.teams[teamId] || {}).edPublic : privateData.edPublic;
|
||||
var isOwner = Array.isArray(owners) && edPublic && owners.indexOf(edPublic) !== -1;
|
||||
if (!isOwner) {
|
||||
// We're not an owner, we shouldn't be able to change the password!
|
||||
waitFor.abort();
|
||||
return void cb({ error: 'EPERM' });
|
||||
}
|
||||
|
||||
var mailbox = oldMetadata.mailbox;
|
||||
if (mailbox) {
|
||||
// Create the encryptors to be able to decrypt and re-encrypt the mailboxes
|
||||
var oldCrypto = Crypto.createEncryptor(oldSecret.keys);
|
||||
var newCrypto = Crypto.createEncryptor(newSecret.keys);
|
||||
|
||||
var m;
|
||||
if (typeof(mailbox) === "string") {
|
||||
try {
|
||||
m = newCrypto.encrypt(oldCrypto.decrypt(mailbox, true, true));
|
||||
} catch (e) {}
|
||||
} else if (mailbox && typeof(mailbox) === "object") {
|
||||
m = {};
|
||||
Object.keys(mailbox).forEach(function (ed) {
|
||||
try {
|
||||
m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
optsPut.metadata.mailbox = m;
|
||||
}
|
||||
|
||||
var expire = oldMetadata.expire;
|
||||
if (expire) {
|
||||
optsPut.metadata.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds
|
||||
}
|
||||
|
||||
// Get last cp (cryptget)
|
||||
Crypt.get(parsed.hash, waitFor(function (err, val) {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({ error: err });
|
||||
}
|
||||
try {
|
||||
cryptgetVal = JSON.parse(val);
|
||||
if (!cryptgetVal.content) {
|
||||
waitFor.abort();
|
||||
return void cb({ error: 'INVALID_CONTENT' });
|
||||
}
|
||||
} catch (e) {
|
||||
waitFor.abort();
|
||||
return void cb({ error: 'CANT_PARSE' });
|
||||
}
|
||||
}), {
|
||||
password: padData.password
|
||||
});
|
||||
}).nThen(function (waitFor) {
|
||||
// Re-encrypt rtchannel
|
||||
oldRtChannel = Util.find(cryptgetVal, ['content', 'channel']);
|
||||
var newCrypto = Crypto.createEncryptor(newSecret.keys);
|
||||
var oldCrypto = Crypto.createEncryptor(oldSecret.keys);
|
||||
var cps = Util.find(cryptgetVal, ['content', 'hashes']);
|
||||
var l = Object.keys(cps).length;
|
||||
var lastCp = l ? cps[l] : {};
|
||||
cryptgetVal.content.hashes = {};
|
||||
common.getHistory({
|
||||
channel: oldRtChannel,
|
||||
lastKnownHash: lastCp.hash
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
console.error(obj);
|
||||
return void cb(obj.error);
|
||||
}
|
||||
var msgs = obj;
|
||||
var newHistory = msgs.map(function (str) {
|
||||
try {
|
||||
var d = oldCrypto.decrypt(str, true, true);
|
||||
return newCrypto.encrypt(d);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
waitFor.abort();
|
||||
return void cb({error: e});
|
||||
}
|
||||
});
|
||||
// Update last knwon hash in cryptgetVal
|
||||
if (lastCp) {
|
||||
lastCp.hash = newHistory[0].slice(0, 64);
|
||||
lastCp.index = 50;
|
||||
cryptgetVal.content.hashes[1] = lastCp;
|
||||
}
|
||||
common.onlyoffice.execCommand({
|
||||
cmd: 'REENCRYPT',
|
||||
data: {
|
||||
channel: newRtChannel,
|
||||
msgs: newHistory,
|
||||
metadata: optsPut.metadata
|
||||
}
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
console.warn(obj);
|
||||
return void cb(obj.error);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
// The new rt channel is ready
|
||||
// The blob uses its own encryption and doesn't need to be reencrypted
|
||||
cryptgetVal.content.channel = newRtChannel;
|
||||
Crypt.put(newHash, JSON.stringify(cryptgetVal), waitFor(function (err) {
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb({ error: err });
|
||||
}
|
||||
}), optsPut);
|
||||
}).nThen(function (waitFor) {
|
||||
pad.leavePad({
|
||||
channel: oldSecret.channel
|
||||
}, waitFor());
|
||||
pad.onDisconnectEvent.fire(true);
|
||||
}).nThen(function (waitFor) {
|
||||
// Set the new password to our pad data
|
||||
common.setPadAttribute('password', newPassword, waitFor(function (err) {
|
||||
if (err) { warning = true; }
|
||||
}), href);
|
||||
common.setPadAttribute('channel', newSecret.channel, waitFor(function (err) {
|
||||
if (err) { warning = true; }
|
||||
}), href);
|
||||
common.setPadAttribute('rtChannel', newRtChannel, waitFor(function (err) {
|
||||
if (err) { warning = true; }
|
||||
}), href);
|
||||
var viewHash = Hash.getViewHashFromKeys(newSecret);
|
||||
newRoHref = '/' + parsed.type + '/#' + viewHash;
|
||||
common.setPadAttribute('roHref', newRoHref, waitFor(function (err) {
|
||||
if (err) { warning = true; }
|
||||
}), href);
|
||||
|
||||
if (parsed.hashData.password && newPassword) { return; } // same hash
|
||||
common.setPadAttribute('href', newHref, waitFor(function (err) {
|
||||
if (err) { warning = true; }
|
||||
}), href);
|
||||
}).nThen(function (waitFor) {
|
||||
// delete the old pad
|
||||
common.removeOwnedChannel({
|
||||
channel: oldSecret.channel,
|
||||
teamId: teamId
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
console.info(obj);
|
||||
return void cb(obj.error);
|
||||
}
|
||||
common.removeOwnedChannel({
|
||||
channel: oldRtChannel,
|
||||
teamId: teamId
|
||||
}, waitFor());
|
||||
}));
|
||||
}).nThen(function () {
|
||||
cb({
|
||||
warning: warning,
|
||||
hash: newHash,
|
||||
href: newHref,
|
||||
roHref: newRoHref
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
common.changeUserPassword = function (Crypt, edPublic, data, cb) {
|
||||
if (!edPublic) {
|
||||
return void cb({
|
||||
@@ -1350,6 +1586,9 @@ define([
|
||||
common.getFullHistory = function (data, cb) {
|
||||
postMessage("GET_FULL_HISTORY", data, cb, {timeout: 180000});
|
||||
};
|
||||
common.getHistory = function (data, cb) {
|
||||
postMessage("GET_HISTORY", data, cb, {timeout: 180000});
|
||||
};
|
||||
common.getHistoryRange = function (data, cb) {
|
||||
postMessage("GET_HISTORY_RANGE", data, cb);
|
||||
};
|
||||
|
||||
@@ -197,7 +197,6 @@ define([
|
||||
'APPLET',
|
||||
'VIDEO', // privacy implications of videos are the same as images
|
||||
'AUDIO', // same with audio
|
||||
'SVG'
|
||||
];
|
||||
var unsafeTag = function (info) {
|
||||
/*if (info.node && $(info.node).parents('media-tag').length) {
|
||||
@@ -307,8 +306,39 @@ define([
|
||||
|
||||
var Dom = domFromHTML($('<div>').append($div).html());
|
||||
$content[0].normalize();
|
||||
$content.find('pre.mermaid[data-processed="true"]').remove();
|
||||
|
||||
var mermaid_source = [];
|
||||
var mermaid_cache = {};
|
||||
|
||||
// iterate over the unrendered mermaid inputs, caching their source as you go
|
||||
$(newDomFixed).find('pre.mermaid').each(function (index, el) {
|
||||
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) {
|
||||
var src = el.childNodes[0].wholeText;
|
||||
el.setAttribute('mermaid-source', src);
|
||||
mermaid_source[index] = src;
|
||||
}
|
||||
});
|
||||
|
||||
// iterate over rendered mermaid charts
|
||||
$content.find('pre.mermaid:not([processed="true"])').each(function (index, el) {
|
||||
// retrieve the attached source code which it was drawn
|
||||
var src = el.getAttribute('mermaid-source');
|
||||
|
||||
// check if that source exists in the set of charts which are about to be rendered
|
||||
if (mermaid_source.indexOf(src) === -1) {
|
||||
// if it's not, then you can remove it
|
||||
if (el.parentNode && el.parentNode.children.length) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
} else if (el.childNodes.length === 1 && el.childNodes[0].nodeType !== 3) {
|
||||
// otherwise, confirm that the content of the rendered chart is not a text node
|
||||
// and keep a copy of it
|
||||
mermaid_cache[src] = el.childNodes[0];
|
||||
}
|
||||
});
|
||||
|
||||
var oldDom = domFromHTML($content[0].outerHTML);
|
||||
|
||||
var patch = makeDiff(oldDom, Dom, id);
|
||||
if (typeof(patch) === 'string') {
|
||||
throw new Error(patch);
|
||||
@@ -348,8 +378,32 @@ define([
|
||||
var target = document.getElementById($a.attr('data-href'));
|
||||
if (target) { target.scrollIntoView(); }
|
||||
});
|
||||
|
||||
// loop over mermaid elements in the rendered content
|
||||
$content.find('pre.mermaid').each(function (index, el) {
|
||||
// since you've simply drawn the content that was supplied via markdown
|
||||
// you can assume that the index of your rendered charts matches that
|
||||
// of those in the markdown source.
|
||||
var src = mermaid_source[index];
|
||||
el.setAttribute('mermaid-source', src);
|
||||
var cached = mermaid_cache[src];
|
||||
|
||||
// check if you had cached a pre-rendered instance of the supplied source
|
||||
if (typeof(cached) !== 'object') { return; }
|
||||
|
||||
// if there's a cached rendering, empty out the contained source code
|
||||
// which would otherwise be drawn again.
|
||||
// apparently this is the fastest way to empty out an element
|
||||
while (el.firstChild) { el.removeChild(el.firstChild); } //el.innerHTML = '';
|
||||
// insert the cached graph
|
||||
el.appendChild(cached);
|
||||
// and set a flag indicating that this graph need not be reprocessed
|
||||
el.setAttribute('data-processed', true);
|
||||
});
|
||||
|
||||
try {
|
||||
Mermaid.init();
|
||||
// finally, draw any graphs which have changed and were thus not cached
|
||||
Mermaid.init(undefined, $content.find('pre.mermaid:not([data-processed="true"])'));
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4187,7 +4187,7 @@ define([
|
||||
}
|
||||
getProperties(el, function (e, $prop) {
|
||||
if (e) { return void logError(e); }
|
||||
UI.alert($prop[0], undefined, true);
|
||||
UI.openCustomModal($prop[0]);
|
||||
});
|
||||
}
|
||||
else if ($this.hasClass("cp-app-drive-context-hashtag")) {
|
||||
|
||||
@@ -1693,7 +1693,7 @@ define([
|
||||
// GET_FULL_HISTORY from sframe-common-outer
|
||||
Store.getFullHistory = function (clientId, data, cb) {
|
||||
var network = store.network;
|
||||
var hkn = network.historyKeeper;
|
||||
var hk = network.historyKeeper;
|
||||
//var crypto = Crypto.createEncryptor(data.keys);
|
||||
// Get the history messages and send them to the iframe
|
||||
var parse = function (msg) {
|
||||
@@ -1708,8 +1708,10 @@ define([
|
||||
var onMsg = function (msg) {
|
||||
if (completed) { return; }
|
||||
var parsed = parse(msg);
|
||||
if (!parsed) { return; }
|
||||
if (parsed[0] === 'FULL_HISTORY_END') {
|
||||
cb(msgs);
|
||||
network.off('message', onMsg);
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
@@ -1726,12 +1728,69 @@ define([
|
||||
}
|
||||
};
|
||||
network.on('message', onMsg);
|
||||
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
|
||||
network.sendto(hk, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
|
||||
};
|
||||
|
||||
Store.getHistory = function (clientId, data, cb) {
|
||||
var network = store.network;
|
||||
var hk = network.historyKeeper;
|
||||
|
||||
var parse = function (msg) {
|
||||
try {
|
||||
return JSON.parse(msg);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
var msgs = [];
|
||||
var completed = false;
|
||||
var onMsg = function (msg, sender) {
|
||||
if (completed) { return; }
|
||||
if (sender !== hk) { return; }
|
||||
var parsed = parse(msg);
|
||||
if (!parsed) { return; }
|
||||
|
||||
// Ignore the metadata message
|
||||
if (parsed.validateKey && parsed.channel) { return; }
|
||||
if (parsed.error && parsed.channel) {
|
||||
if (parsed.channel === data.channel) {
|
||||
network.off('message', onMsg);
|
||||
completed = true;
|
||||
cb({error: parsed.error});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// End of history: cb
|
||||
if (parsed.state === 1 && parsed.channel) {
|
||||
if (parsed.channel !== data.channel) { return; }
|
||||
cb(msgs);
|
||||
network.off('message', onMsg);
|
||||
completed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
msg = parsed[4];
|
||||
// Keep only the history for our channel
|
||||
if (parsed[3] !== data.channel) { return; }
|
||||
if (msg) {
|
||||
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
|
||||
msgs.push(msg);
|
||||
}
|
||||
};
|
||||
network.on('message', onMsg);
|
||||
|
||||
var cfg = {
|
||||
lastKnownHash: data.lastKnownHash
|
||||
};
|
||||
var msg = ['GET_HISTORY', data.channel, cfg];
|
||||
network.sendto(hk, JSON.stringify(msg));
|
||||
};
|
||||
|
||||
Store.getHistoryRange = function (clientId, data, cb) {
|
||||
var network = store.network;
|
||||
var hkn = network.historyKeeper;
|
||||
var hk = network.historyKeeper;
|
||||
var parse = function (msg) {
|
||||
try {
|
||||
return JSON.parse(msg);
|
||||
@@ -1779,7 +1838,7 @@ define([
|
||||
};
|
||||
|
||||
network.on('message', onMsg);
|
||||
network.sendto(hkn, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
|
||||
network.sendto(hk, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
|
||||
from: data.lastKnownHash,
|
||||
cpCount: 2,
|
||||
txid: txid
|
||||
|
||||
@@ -200,6 +200,41 @@ define([
|
||||
}));
|
||||
};
|
||||
|
||||
var reencrypt = function (ctx, data, cId, cb) {
|
||||
var channel = data.channel;
|
||||
var network = ctx.store.network;
|
||||
|
||||
var onOpen = function (wc) {
|
||||
var hk = network.historyKeeper;
|
||||
var cfg = {
|
||||
metadata: data.metadata
|
||||
};
|
||||
var msg = ['GET_HISTORY', wc.id, cfg];
|
||||
network.sendto(hk, JSON.stringify(msg));
|
||||
data.msgs.forEach(function (msg) {
|
||||
wc.bcast(msg);
|
||||
});
|
||||
wc.leave();
|
||||
cb();
|
||||
};
|
||||
|
||||
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", channel, function (e, response) {
|
||||
if (e) { return void cb({error: e}); }
|
||||
var isNew;
|
||||
if (response && response.length && typeof(response[0]) === 'boolean') {
|
||||
isNew = response[0];
|
||||
} else {
|
||||
cb({error: 'INVALID_RESPONSE'});
|
||||
}
|
||||
if (!isNew) { return void cb({error: 'EEXISTS'}); }
|
||||
|
||||
// Channel is new: we can push our reencrypted history
|
||||
network.join(channel).then(onOpen, function (err) {
|
||||
return void cb({error: err});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var leaveChannel = function (ctx, padChan) {
|
||||
// Leave channel and prevent reconnect when we leave a pad
|
||||
Object.keys(ctx.channels).some(function (ooChan) {
|
||||
@@ -267,6 +302,9 @@ define([
|
||||
if (cmd === 'OPEN_CHANNEL') {
|
||||
return void openChannel(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'REENCRYPT') {
|
||||
return void reencrypt(ctx, data, clientId, cb);
|
||||
}
|
||||
};
|
||||
|
||||
return oo;
|
||||
|
||||
@@ -73,6 +73,7 @@ define([
|
||||
JOIN_PAD: Store.joinPad,
|
||||
LEAVE_PAD: Store.leavePad,
|
||||
GET_FULL_HISTORY: Store.getFullHistory,
|
||||
GET_HISTORY: Store.getHistory,
|
||||
GET_HISTORY_RANGE: Store.getHistoryRange,
|
||||
IS_NEW_CHANNEL: Store.isNewChannel,
|
||||
REQUEST_PAD_ACCESS: Store.requestPadAccess,
|
||||
|
||||
@@ -484,10 +484,29 @@ define([
|
||||
Cryptpad.mailbox.execCommand(data, cb);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) {
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
cb();
|
||||
});
|
||||
|
||||
sframeChan.on('Q_STORE_IN_TEAM', function (data, cb) {
|
||||
Cryptpad.storeInTeam(data, cb);
|
||||
});
|
||||
|
||||
sframeChan.on('EV_GOTO_URL', function (url) {
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
sframeChan.on('EV_OPEN_URL', function (url) {
|
||||
if (url) {
|
||||
window.open(url);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
addCommonRpc(sframeChan);
|
||||
|
||||
@@ -634,11 +653,6 @@ define([
|
||||
Notifier.notify(data);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) {
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
cb();
|
||||
});
|
||||
|
||||
sframeChan.on('Q_MOVE_TO_TRASH', function (data, cb) {
|
||||
cb = cb || $.noop;
|
||||
if (readOnly && hashes.editHash) {
|
||||
@@ -956,20 +970,6 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
sframeChan.on('EV_GOTO_URL', function (url) {
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
sframeChan.on('EV_OPEN_URL', function (url) {
|
||||
if (url) {
|
||||
window.open(url);
|
||||
}
|
||||
});
|
||||
|
||||
sframeChan.on('Q_PIN_GET_USAGE', function (teamId, cb) {
|
||||
Cryptpad.isOverPinLimit(teamId, function (err, overLimit, data) {
|
||||
cb({
|
||||
@@ -1008,6 +1008,11 @@ define([
|
||||
}, cb);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
|
||||
data.href = data.href || window.location.href;
|
||||
Cryptpad.changeOOPassword(data, cb);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
|
||||
data.href = data.href || window.location.href;
|
||||
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
|
||||
|
||||
@@ -533,7 +533,7 @@ MessengerUI, Messages) {
|
||||
Common.getSframeChannel().event('EV_SHARE_OPEN', {
|
||||
hidden: true
|
||||
});
|
||||
$shareBlock.click(function () {
|
||||
$shareBlock.click(function () {
|
||||
var title = (config.title && config.title.getTitle && config.title.getTitle())
|
||||
|| (config.title && config.title.defaultName)
|
||||
|| "";
|
||||
|
||||
@@ -754,6 +754,10 @@
|
||||
"crypto": {
|
||||
"q": "Welche Kryptografie benutzt ihr?",
|
||||
"a": "CryptPad basiert auf zwei quelloffenen Kryptografiebibliotheken: <a href='https://github.com/dchest/tweetnacl-js' target='_blank'>tweetnacl.js</a> und <a href='https://github.com/dchest/scrypt-async-js' target='_blank'>scrypt-async.js</a>.<br><br>Scrypt ist eine <em>Passwort-basierte Schlüsselableitungsfunktion</em>. Wir werden sie, um deinen Benutzernamen und dein Passwort in ein einzigartiges Schlüsselpaar umzuwandeln. Dieses sichert den Zugang zu deinem CryptDrive, so dass nur du auf die Liste deiner Pads zugreifen kannst.<br><br> Wir verwenden die Verschlüsselung <em>xsalsa20-poly1305</em> und <em>x25519-xsalsa20-poly1305</em> von tweetnacl, um Dokumente und den Chatverlauf zu verschlüsseln."
|
||||
},
|
||||
"pad_password": {
|
||||
"q": "Was passiert, wenn ich einen Ordner oder ein Pad mit einem Passwort schütze?",
|
||||
"a": "Du kannst Pads oder geteilte Ordner bei der Erstellung mit einem Passwort schützen. Du kannst auch jederzeit im Eigenschaften-Menü ein Passwort setzen/ändern/entfernen.<br><br>Passwörter für Pads und geteilte Ordner sollen den Link schützen, wenn du ihn über einen unsicheren Kanal wie Mail oder Textnachricht teilst. Wenn jemand den Link abfängt, aber nicht das Passwort kennt, kann er nicht auf dein Dokument zugreifen.<br><br>Beim Teilen mit Kontakten oder Teams innerhalb von CryptPad wird die Kommunikation verschlüsselt und es wird angenommen, dass der Empfänger auf das Dokument zugreifen darf. Daher wird das Passwort zusammen mit dem Pad geteilt. Der Empfänger, und du selbst, werden beim Öffnen des Dokuments <b>nicht</b> danach gefragt."
|
||||
}
|
||||
},
|
||||
"usability": {
|
||||
@@ -1066,7 +1070,7 @@
|
||||
"notification_folderShared": "{0} hat einen Ordner mit dir geteilt: <b>{1}</b>",
|
||||
"share_selectAll": "Alle auswählen",
|
||||
"share_filterFriend": "Nach Namen suchen",
|
||||
"share_linkFriends": "Mit Freunden teilen",
|
||||
"share_linkFriends": "Mit Kontakten teilen",
|
||||
"share_withFriends": "Teilen",
|
||||
"share_deselectAll": "Alle abwählen",
|
||||
"notifications_dismiss": "Verbergen",
|
||||
@@ -1160,7 +1164,7 @@
|
||||
"owner_request_declined": "{0} hat deine Einladung abgelehnt, ein Eigentümer von <b>{1}</b> zu sein",
|
||||
"owner_removed": "{0} hat dich als Eigentümer von <b>{1}</b> entfernt",
|
||||
"owner_removedPending": "{0} hat die Einladung zur Eigentümerschaft von <b>{1}</b> zurückgezogen",
|
||||
"share_linkTeam": "Mit einem Team teilen",
|
||||
"share_linkTeam": "Zu Team-Drive hinzufügen",
|
||||
"team_inviteModalButton": "Einladen",
|
||||
"team_pickFriends": "Freunde auswählen, um sie in dieses Team einzuladen",
|
||||
"team_noFriend": "Du bist derzeit mit keinen Freunden auf CryptPad verbunden.",
|
||||
@@ -1242,5 +1246,13 @@
|
||||
"teams_table_owners": "Team verwalten",
|
||||
"teams_table_role": "Rolle",
|
||||
"share_contactCategory": "Kontakte",
|
||||
"pad_wordCount": "Wörter: {0}"
|
||||
"pad_wordCount": "Wörter: {0}",
|
||||
"share_linkWarning": "Dieser Link enthält die Schlüssel zu deinem Dokument. Empfänger erhalten unwiderruflichen Zugriff zu deinen Inhalten.",
|
||||
"share_linkPasswordAlert": "Dieses Element ist passwortgeschützt. Wenn du diesen Link teilst, muss der Empfänger das Passwort eingeben.",
|
||||
"share_contactPasswordAlert": "Dieses Element ist passwortgeschützt. Weil du es mit einem CryptPad-Kontakt teilst, muss der Empfänger das Passwort nicht eingeben.",
|
||||
"share_embedPasswordAlert": "Dieses Element ist passwortgeschützt. Wenn du dieses Pad einbettest, werden Betrachter nach dem Passwort gefragt.",
|
||||
"passwordFaqLink": "Mehr über Passwörter erfahren",
|
||||
"share_noContactsLoggedIn": "Du hast noch keine Kontakte bei CryptPad. Teile den Link zu deinem Profil, damit andere dir Kontaktanfragen senden können.",
|
||||
"share_copyProfileLink": "Profil-Link kopieren",
|
||||
"share_noContactsNotLoggedIn": "Logge dich ein oder registriere dich, um deine Kontakte zu sehen und neue hinzuzufügen."
|
||||
}
|
||||
|
||||
@@ -1,2 +1,37 @@
|
||||
{
|
||||
"type": {
|
||||
"pad": "Teksti",
|
||||
"code": "Koodi",
|
||||
"poll": "Kysely",
|
||||
"kanban": "Kanban",
|
||||
"slide": "Esitys",
|
||||
"drive": "CryptDrive",
|
||||
"whiteboard": "Tussitaulu",
|
||||
"file": "Tiedosto",
|
||||
"media": "Media",
|
||||
"todo": "Tehtävälista",
|
||||
"contacts": "Yhteystiedot",
|
||||
"sheet": "Taulukko (Beta)",
|
||||
"teams": "Teams"
|
||||
},
|
||||
"button_newpad": "Uusi Teksti-padi",
|
||||
"button_newcode": "Uusi Koodi-padi",
|
||||
"button_newpoll": "Uusi Kysely",
|
||||
"button_newslide": "Uusi Esitys",
|
||||
"button_newwhiteboard": "Uusi Tussitaulu",
|
||||
"button_newkanban": "Uusi Kanban",
|
||||
"button_newsheet": "Uusi Taulukko",
|
||||
"common_connectionLost": "<b>Yhteys palvelimelle katkennut</b><br>Sovellus on vain luku-tilassa, kunnes yhteys palaa.",
|
||||
"websocketError": "Yhdistäminen websocket-palvelimelle epäonnistui...",
|
||||
"typeError": "Tämä padi ei ole yhteensopiva valitun sovelluksen kanssa",
|
||||
"onLogout": "Olet kirjautunut ulos, {0}klikkaa tästä{1} kirjautuaksesi sisään tai paina <em>Esc-näppäintä</em> käyttääksesi padia vain luku-tilassa.",
|
||||
"wrongApp": "Reaaliaikaisen sisällön näyttäminen selaimessa epäonnistui. Ole hyvä ja yritä sivun lataamista uudelleen.",
|
||||
"padNotPinned": "Tämä padi vanhenee kolmen kuukauden käyttämättömyyden jälkeen, {0}kirjaudu sisään{1} tai [2}rekisteröidy{3} säilyttääksesi sen.",
|
||||
"padNotPinnedVariable": "Tämä padi vanhenee {4} päivän käyttämättömyyden jälkeen, {0}kirjaudu sisään{1} tai {2}rekisteröidy{3} säilyttääksesi sen.",
|
||||
"anonymousStoreDisabled": "Tämän CryptPad-instanssin ylläpitäjä on estänyt anonyymien käyttäjien pääsyn tallennustilaan. Kirjaudu sisään käyttääksesi CryptDrivea.",
|
||||
"expiredError": "Tämä padi on vanhentunut, eikä se ole enää saatavilla.",
|
||||
"deletedError": "Tämä padi on poistettu omistajansa toimesta, eikä se ole enää saatavilla.",
|
||||
"inactiveError": "Tämä padi on poistettu käyttämättömyyden vuoksi. Paina Esc-näppäintä luodaksesi uuden padin.",
|
||||
"chainpadError": "Sisältöä päivitettäessä tapahtui vakava virhe. Tämä sivu on vain luku-tilassa, jotta tekemäsi muutokset eivät katoaisi.<br>Paina <em>Esc-näppäintä</em> jatkaaksesi padin katselua vain luku-tilassa, tai lataa sivu uudelleen yrittääksesi muokkaamista.",
|
||||
"invalidHashError": "Pyytämäsi dokumentin URL-osoite on virheellinen."
|
||||
}
|
||||
|
||||
@@ -761,6 +761,10 @@
|
||||
"crypto": {
|
||||
"q": "Quelle cryptographie utilisez-vous ?",
|
||||
"a": "CryptPad est basé sur deux librairies open-source de cryptographie : <a href=\"https://github.com/dchest/tweetnacl-js\" target=\"_blank\">tweetnacl.js</a> et <a href=\"https://github.com/dchest/scrypt-async-js\" target=\"_blank\">scrypt-async.js</a>.<br><b>Scrypt</b> est une <em>fonction de dérivation de clé</em> basée sur un mot de passe. Nous l'utilisons pour transformer votre nom d'utilisateur et votre mot de passe en un unique ensemble de clés qui sécurise l'accès à votre CryptDrive afin que vous seul puissiez accéder à votre liste de pads.<br>Nous utilisons les outils de chiffrement <em>xsalsa20-poly1305</em> et <em>x25519-xsalsa20-poly1305</em> fournis par <b>tweetnacl</b> pour chiffrer vos pads et l'historique du chat respectivement."
|
||||
},
|
||||
"pad_password": {
|
||||
"q": "Que se passe t'il quand je protège un pad/dossier avec un mot de passe ?",
|
||||
"a": "Vous pouvez protéger tout nouveau pad ou dossier avec un mot de passe. Vous pouvez aussi utiliser le menu <em>propriétés</em> pour ajouter/changer/supprimer un mot de passe par la suite. <br><br>Les mots de passe sur les pads et dossiers partagés sont faits pour protéger les liens quand ils sont envoyés de manière non sécurisée (par exemple par email ou SMS). Si quelqu'un intercepte le lien sans avoir le mot de passe, ils n'auront pas accès à votre document.<br><br>Quand vous partagez avec vos contacts ou équipes sur CryptPad, les communications sont chiffrées et nous partons du principe que vous voulez donner l'accès. C'est pourquoi le mot de passe est alors stocké et envoyé avec le pad quand vous l'envoyez. Les destinataires, ou vous même, n'ont <b>pas</b> a le donner pour ouvrir le document."
|
||||
}
|
||||
},
|
||||
"usability": {
|
||||
@@ -1072,7 +1076,7 @@
|
||||
"share_selectAll": "Tout",
|
||||
"share_deselectAll": "Aucun",
|
||||
"share_filterFriend": "Rechercher par nom",
|
||||
"share_linkFriends": "Partager avec des amis",
|
||||
"share_linkFriends": "Partager avec des contacts",
|
||||
"share_withFriends": "Partager",
|
||||
"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.",
|
||||
@@ -1162,7 +1166,7 @@
|
||||
"owner_removed": "{0} a supprimé vos droits de propriétaire de <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.",
|
||||
"share_linkTeam": "Partager avec une équipe",
|
||||
"share_linkTeam": "Ajouter au CryptDrive d'une équipe",
|
||||
"team_pickFriends": "Choisissez les amis à inviter dans cette équipe",
|
||||
"team_inviteModalButton": "Inviter",
|
||||
"team_noFriend": "Vous n'avez pas encore ajouté d'ami sur CryptPad.",
|
||||
@@ -1242,5 +1246,18 @@
|
||||
"teams_table_owners": "Gérer l'équipe",
|
||||
"teams_table_role": "Rôle",
|
||||
"share_contactCategory": "Contacts",
|
||||
"pad_wordCount": "Mots : {0}"
|
||||
"pad_wordCount": "Mots : {0}",
|
||||
"share_linkWarning": "Ce lien contient les clés de votre document. Le destinataire aura un accès irrévocable à votre contenu.",
|
||||
"share_linkPasswordAlert": "Cet élément est protégé par un mot de passe. Quand vous partagez le lien, le destinataire doit saisir le mot de passe.",
|
||||
"share_contactPasswordAlert": "Cet élément est protégé par un mot de passe. Quand vous le partagez avec un contact CryptPad, le destinataire n'a pas a saisir le mot de passe.",
|
||||
"share_embedPasswordAlert": "Cet élément est protégé par un mot de passe. Quand vous l'intégrez à une page, les visiteurs doivent saisir le mot de passe.",
|
||||
"passwordFaqLink": "En lire plus sur les mots de passe",
|
||||
"share_noContactsLoggedIn": "Vous n'avez pas encore ajouté de contacts sur CryptPad. Partagez le lien de votre profil pour que l'on vous envoie des demandes de contact.",
|
||||
"share_copyProfileLink": "Copier le lien du profil",
|
||||
"share_noContactsNotLoggedIn": "Connectez-vous ou enregistrez-vous pour voir vos contacts ou en ajouter de nouveaux.",
|
||||
"contacts_mute": "Masquer",
|
||||
"contacts_unmute": "Réafficher",
|
||||
"contacts_manageMuted": "Comptes masqués",
|
||||
"contacts_mutedUsers": "Comptes masqués",
|
||||
"contacts_muteInfo": "Vous ne receverez plus de notifications ou de messages si vous masquez ce compte.<br>L'utilisateur ne sera pas informé que vous l'avez masqué. "
|
||||
}
|
||||
|
||||
@@ -779,6 +779,10 @@
|
||||
"crypto": {
|
||||
"q": "What cryptography do you use?",
|
||||
"a": "CryptPad is based upon two open-source cryptography libraries: <a href='https://github.com/dchest/tweetnacl-js' target='_blank'>tweetnacl.js</a> and <a href='https://github.com/dchest/scrypt-async-js' target='_blank'>scrypt-async.js</a>.<br><br>Scrypt is a <em>password-based key derivation algorithm</em>. We use it to turn your username and password into a unique keyring which secures access to your CryptDrive such that only you can access your list of pads.<br><br>We use the <em>xsalsa20-poly1305</em> and <em>x25519-xsalsa20-poly1305</em> cyphers provided by tweetnacl to encrypt pads and chat history, respectively."
|
||||
},
|
||||
"pad_password": {
|
||||
"q": "What happens when I protect a pad/folder with a password?",
|
||||
"a": "You can protect any pad or shared folder with a password when you create it. You can also use the properties menu to set/change/remove a password at any time.<br><br>Pad and shared folder passwords are intended to protect the link when you share it over potentially insecure channels such as email or text message. If someone intercepts your link but does not have the password they will not be able to read your document.<br><br>When sharing within CryptPad with your contacts or teams, communications are encrypted and we assume you want them to access your document. Therefore the password is remembered and sent with the pad when you share it. The recipient, or yourself, are <b>not</b> asked for it when they open the document."
|
||||
}
|
||||
},
|
||||
"usability": {
|
||||
@@ -1089,7 +1093,7 @@
|
||||
"share_selectAll": "Select all",
|
||||
"share_deselectAll": "Deselect all",
|
||||
"share_filterFriend": "Search by name",
|
||||
"share_linkFriends": "Share with friends",
|
||||
"share_linkFriends": "Share with contacts",
|
||||
"share_withFriends": "Share",
|
||||
"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.",
|
||||
@@ -1164,7 +1168,7 @@
|
||||
"owner_request_declined": "{0} has declined your offer to be an owner 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>",
|
||||
"share_linkTeam": "Share with a team",
|
||||
"share_linkTeam": "Add to team drive",
|
||||
"team_pickFriends": "Choose which friends to invite to this team",
|
||||
"team_inviteModalButton": "Invite",
|
||||
"team_noFriend": "You haven't connected with any friends on CryptPad yet.",
|
||||
@@ -1242,5 +1246,18 @@
|
||||
"teams_table_admins": "Manage members",
|
||||
"teams_table_owners": "Manage team",
|
||||
"teams_table_role": "Role",
|
||||
"pad_wordCount": "Words: {0}"
|
||||
"pad_wordCount": "Words: {0}",
|
||||
"share_linkWarning": "This link contains the keys to your document. Recipients will gain non-revokable access to your content.",
|
||||
"share_linkPasswordAlert": "This item is password protected. When you send the link the recipient will have to enter the password.",
|
||||
"share_contactPasswordAlert": "This item is password protected. Because you are sharing it with a CryptPad contact, the recipient will not have to enter the password.",
|
||||
"share_embedPasswordAlert": "This item is password protected. When you embed this pad, viewers will be asked for the password.",
|
||||
"passwordFaqLink": "Read more about passwords",
|
||||
"share_noContactsLoggedIn": "You are not connected with anyone on CryptPad yet. Share the link to your profile for people to send you contact requests.",
|
||||
"share_copyProfileLink": "Copy profile link",
|
||||
"share_noContactsNotLoggedIn": "Log in or register to see your existing contacts and add new ones.",
|
||||
"contacts_mute": "Mute",
|
||||
"contacts_unmute": "Unmute",
|
||||
"contacts_manageMuted": "Manage muted",
|
||||
"contacts_mutedUsers": "Muted accounts",
|
||||
"contacts_muteInfo": "You will not receive any notifications or messages from muted users.<br>They will not know you have muted them. "
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user