Merge branch 'staging' into cba

This commit is contained in:
yflory
2020-04-17 15:17:24 +02:00
33 changed files with 702 additions and 807 deletions

View File

@@ -196,6 +196,7 @@ define([
]);
var $frame = $(frame);
frame.closeModal = function (cb) {
frame.closeModal = function () {}; // Prevent further calls
$frame.fadeOut(150, function () {
$frame.detach();
if (typeof(cb) === "function") { cb(); }
@@ -464,16 +465,23 @@ define([
UI.createModal = function (cfg) {
var $body = cfg.$body || $('body');
var $blockContainer = $body.find('#'+cfg.id);
if (!$blockContainer.length) {
$blockContainer = $(h('div.cp-modal-container#'+cfg.id, {
var $blockContainer = cfg.id && $body.find('#'+cfg.id);
if (!$blockContainer || !$blockContainer.length) {
var id = '';
if (cfg.id) { id = '#'+cfg.id; }
$blockContainer = $(h('div.cp-modal-container'+id, {
tabindex: 1
}));
}
var deleted = false;
var hide = function () {
if (cfg.onClose) { return void cfg.onClose(); }
if (deleted) { return; }
$blockContainer.hide();
if (cfg.onClosed) { cfg.onClosed(); }
if (!cfg.id) {
deleted = true;
$blockContainer.remove();
}
if (cfg.onClose) { cfg.onClose(); }
};
$blockContainer.html('').appendTo($body);
var $block = $(h('div.cp-modal')).appendTo($blockContainer);

View File

@@ -175,9 +175,9 @@ define([
var common = config.common;
var sframeChan = common.getSframeChannel();
var title = config.title;
var friends = config.friends;
var friends = config.friends || {};
var teams = config.teams || {};
var myName = common.getMetadataMgr().getUserData().name;
if (!friends) { return; }
var order = [];
var smallCurves = Object.keys(friends).map(function (c) {
@@ -206,40 +206,28 @@ define([
delete friends[curve];
});
var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, {
common: common,
data: friends,
noFilter: false,
large: true
}, refreshButtons);
var friendDiv = friendsList.div;
$div.append(friendDiv);
var others = friendsList.icons;
var others = [];
if (Object.keys(friends).length) {
var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, {
common: common,
data: friends,
noFilter: false,
large: true
}, refreshButtons);
var friendDiv = friendsList.div;
$div.append(friendDiv);
others = friendsList.icons;
}
var privateData = common.getMetadataMgr().getPrivateData();
var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {};
var teams = {};
Object.keys(teamsData).forEach(function (id) {
// config.teamId only exists when we're trying to share a pad from a team drive
// In this case, we don't want to share the pad with the current team
if (config.teamId && config.teamId === id) { return; }
if (!teamsData[id].hasSecondaryKey) { return; }
var t = teamsData[id];
teams[t.edPublic] = {
notifications: true,
displayName: t.name,
edPublic: t.edPublic,
avatar: t.avatar,
id: id
};
});
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
common: common,
noFilter: true,
large: true,
data: teams
}, refreshButtons);
$div.append(teamsList.div);
if (Object.keys(teams).length) {
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
common: common,
noFilter: true,
large: true,
data: teams
}, refreshButtons);
$div.append(teamsList.div);
}
var shareButton = {
className: 'primary cp-share-with-friends',
@@ -395,6 +383,26 @@ define([
}
};
var getEditableTeams = function (common, config) {
var privateData = common.getMetadataMgr().getPrivateData();
var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {};
var teams = {};
Object.keys(teamsData).forEach(function (id) {
// config.teamId only exists when we're trying to share a pad from a team drive
// In this case, we don't want to share the pad with the current team
if (config.teamId && config.teamId === id) { return; }
if (!teamsData[id].hasSecondaryKey) { return; }
var t = teamsData[id];
teams[t.edPublic] = {
notifications: true,
displayName: t.name,
edPublic: t.edPublic,
avatar: t.avatar,
id: id
};
});
return teams;
};
var makeBurnAfterReadingUrl = function (common, href, channel, cb) {
var keyPair = Hash.generateSignPair();
var parsed = Hash.parsePadUrl(href);
@@ -643,7 +651,10 @@ define([
// Share with contacts tab
var hasFriends = Object.keys(config.friends || {}).length !== 0;
var teams = getEditableTeams(common, config);
config.teams = teams;
var hasFriends = Object.keys(config.friends || {}).length ||
Object.keys(teams).length;
var onFriendShare = Util.mkEvent();
@@ -926,7 +937,10 @@ define([
});
// share with contacts tab
var hasFriends = Object.keys(config.friends || {}).length !== 0;
var teams = getEditableTeams(common, config);
config.teams = teams;
var hasFriends = Object.keys(config.friends || {}).length ||
Object.keys(teams).length;
var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : noContactsMessage(common);
var friendsList = friendsObject.content;
@@ -1592,11 +1606,7 @@ define([
.text(Messages.accessButton))
.click(common.prepareFeedback(type))
.click(function () {
require(['/common/inner/access.js'], function (Access) {
Access.getAccessModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
sframeChan.event('EV_ACCESS_OPEN');
});
break;
case 'properties':
@@ -1611,11 +1621,7 @@ define([
if (!data) {
return void UI.alert(Messages.autostore_notAvailable);
}
require(['/common/inner/properties.js'], function (Properties) {
Properties.getPropertiesModal(common, {}, function (e) {
if (e) { console.error(e); }
});
});
sframeChan.event('EV_PROPERTIES_OPEN');
});
});
break;
@@ -2628,17 +2634,13 @@ define([
});
};
UIElements.initFilePicker = function (common, cfg) {
var onSelect = cfg.onSelect || $.noop;
UIElements.openFilePicker = function (common, types, cb) {
var sframeChan = common.getSframeChannel();
sframeChan.on("EV_FILE_PICKED", function (data) {
onSelect(data);
sframeChan.query("Q_FILE_PICKER_OPEN", types, function (err, data) {
if (err) { return; }
cb(data);
});
};
UIElements.openFilePicker = function (common, types) {
var sframeChan = common.getSframeChannel();
sframeChan.event("EV_FILE_PICKER_OPEN", types);
};
UIElements.openTemplatePicker = function (common, force) {
var metadataMgr = common.getMetadataMgr();
@@ -2661,29 +2663,25 @@ define([
return;
}
delete pickerCfg.hidden;
common.openFilePicker(pickerCfg);
var first = true; // We can only pick a template once (for a new document)
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === type && first) {
UI.addLoadingScreen({hideTips: true});
var chatChan = common.getPadChat();
var cursorChan = common.getCursorChannel();
sframeChan.query('Q_TEMPLATE_USE', {
href: data.href,
chat: chatChan,
cursor: cursorChan
}, function () {
first = false;
UI.removeLoadingScreen();
Feedback.send('TEMPLATE_USED');
});
if (focus) { focus.focus(); }
return;
}
common.openFilePicker(pickerCfg, function (data) {
if (data.type === type && first) {
UI.addLoadingScreen({hideTips: true});
var chatChan = common.getPadChat();
var cursorChan = common.getCursorChannel();
sframeChan.query('Q_TEMPLATE_USE', {
href: data.href,
chat: chatChan,
cursor: cursorChan
}, function () {
first = false;
UI.removeLoadingScreen();
Feedback.send('TEMPLATE_USED');
});
if (focus) { focus.focus(); }
return;
}
};
common.initFilePicker(fileDialogCfg);
});
};
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {

View File

@@ -23,8 +23,17 @@ define([
init: function () {}
};
var mermaidThemeCSS = ".node rect { fill: #DDD; stroke: #AAA; } " +
"rect.task, rect.task0, rect.task2 { stroke-width: 1 !important; rx: 0 !important; } " +
"g.grid g.tick line { opacity: 0.25; }" +
"g.today line { stroke: red; stroke-width: 1; stroke-dasharray: 3; opacity: 0.5; }";
require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) {
Mermaid = _Mermaid;
Mermaid.initialize({
gantt: { axisFormat: '%m-%d', },
"themeCSS": mermaidThemeCSS,
});
});
var highlighter = function () {
@@ -351,6 +360,12 @@ define([
// retrieve the attached source code which it was drawn
var src = el.getAttribute('mermaid-source');
/* The new source might have syntax errors that will prevent rendering.
It might be preferable to keep the existing state instead of removing it
if you don't have something better to display. Ideally we should display
the cause of the syntax error so that the user knows what to correct. */
//if (!Mermaid.parse(src)) { } // TODO
// 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
@@ -418,7 +433,7 @@ define([
throw new Error(patch);
} else {
DD.apply($content[0], patch);
var $mts = $content.find('media-tag:not(:has(*))');
var $mts = $content.find('media-tag');
$mts.each(function (i, el) {
var $mt = $(el).contextmenu(function (e) {
e.preventDefault();
@@ -426,6 +441,16 @@ define([
$(contextMenu.menu).find('li').show();
contextMenu.show(e);
});
if ($mt.children().length) {
$mt.off('dblclick preview');
$mt.on('preview', onPreview($mt));
if ($mt.find('img').length) {
$mt.on('dblclick', function () {
$mt.trigger('preview');
});
}
return;
}
MediaTag(el);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {

View File

@@ -59,7 +59,7 @@ define([
}), opts.href);
// If this is a file, don't try to look for metadata
if (opts.channel && opts.channel.length > 34) { return; }
if (opts.channel && opts.channel.length > 32) { return; }
if (opts.channel) { data.channel = opts.channel; }
Modal.loadMetadata(Env, data, waitFor);
}).nThen(function () {
@@ -129,7 +129,10 @@ define([
tabs[i] = {
content: c && UI.dialog.customModal(node, {
buttons: obj.buttons || button,
onClose: function () { blocked = false; }
onClose: function () {
blocked = false;
if (typeof(opts.onClose) === "function") { opts.onClose(); }
}
}),
disabled: !c,
title: obj.title,

View File

@@ -132,6 +132,12 @@ define([
APP.onLocal();
};
var isRegisteredUserOnline = function () {
var users = metadataMgr.getMetadata().users || {};
return Object.keys(users).some(function (id) {
return users[id] && users[id].curvePublic;
});
};
var isUserOnline = function (ooid) {
// Remove ids for users that have left the channel
deleteOffline();
@@ -348,16 +354,44 @@ define([
fixSheets();
APP.FM.handleFile(blob, data);
};
Messages.oo_login = 'Log in...'; // XXX
var noLogin = false;
var makeCheckpoint = function (force) {
if (!common.isLoggedIn()) { return; }
var locked = content.saveLock;
var lastCp = getLastCp();
var needCp = force || ooChannel.cpIndex % CHECKPOINT_INTERVAL === 0 ||
(ooChannel.cpIndex - lastCp.index) > CHECKPOINT_INTERVAL;
(ooChannel.cpIndex - (lastCp.index || 0)) > CHECKPOINT_INTERVAL;
if (!needCp) { return; }
if (!locked || !isUserOnline(locked) || force) {
if (!common.isLoggedIn() && !isRegisteredUserOnline() && !noLogin) {
var login = h('button.cp-corner-primary', Messages.login_login);
var register = h('button.cp-corner-primary', Messages.login_register);
var cancel = h('button.cp-corner-cancel', Messages.cancel);
var actions = h('div', [cancel, register, login]);
var modal = UI.cornerPopup(Messages.oo_login, actions, '', {alt: true});
$(register).click(function () {
common.setLoginRedirect(function () {
common.gotoURL('/register/');
});
modal.delete();
});
$(login).click(function () {
common.setLoginRedirect(function () {
common.gotoURL('/login/');
});
modal.delete();
});
$(cancel).click(function () {
modal.delete();
noLogin = true;
});
return;
}
if (!common.isLoggedIn()) { return; }
content.saveLock = myOOId;
APP.onLocal();
APP.realtime.onSettle(function () {
@@ -907,8 +941,16 @@ define([
if (ifr) { ifr.remove(); }
};
common.initFilePicker({
onSelect: function (data) {
APP.AddImage = function(cb1, cb2) {
APP.AddImageSuccessCallback = cb1;
APP.AddImageErrorCallback = cb2;
common.openFilePicker({
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
}
}, function (data) {
if (data.type !== 'file') {
debug("Unexpected data type picked " + data.type);
return;
@@ -929,19 +971,6 @@ define([
});
});
});
}
});
APP.AddImage = function(cb1, cb2) {
APP.AddImageSuccessCallback = cb1;
APP.AddImageErrorCallback = cb2;
common.openFilePicker({
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
}
});
};
@@ -1091,12 +1120,12 @@ define([
var x2tSaveAndConvertData = function(data, filename, extension, finalFilename) {
// Perform the x2t conversion
require(['/common/onlyoffice/x2t/x2t.js'], function() {
require(['/common/onlyoffice/x2t/x2t.js'], function() { // XXX why does this fail without an access-control-allow-origin header?
var x2t = window.Module;
x2t.run();
if (x2tInitialized) {
debug("x2t runtime already initialized");
x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalFilename);
x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalFilename); // XXX shouldn't this return ?
}
x2t.onRuntimeInitialized = function() {

View File

@@ -675,6 +675,8 @@ define([
sem.take(function (give) {
var otherOwners = false;
nThen(function (_w) {
// Don't check server metadata for blobs
if (c.length !== 32) { return; }
Store.anonRpcMsg(null, {
msg: 'GET_METADATA',
data: c
@@ -1807,6 +1809,7 @@ define([
var cb = Util.once(Util.mkAsync(_cb));
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (data.channel.length !== 32) { return void cb({ error: 'EINVAL'}); }
store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) {
if (err) { return void cb({error: err}); }
var metadata = (obj && obj[0]) || {};

View File

@@ -678,6 +678,8 @@ define([
sem.take(function (give) {
var otherOwners = false;
nThen(function (_w) {
// Don't check server metadata for blobs
if (c.length !== 32) { return; }
ctx.Store.anonRpcMsg(null, {
msg: 'GET_METADATA',
data: c

View File

@@ -503,8 +503,13 @@ define([
var createFilePicker = function () {
if (!common.isLoggedIn()) { return; }
common.initFilePicker({
onSelect: function (data) {
$embedButton = common.createButton('mediatag', true).click(function () {
var cfg = {
types: ['file'],
where: ['root']
};
if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); }
common.openFilePicker(cfg, function (data) {
if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type);
return;
@@ -516,17 +521,7 @@ define([
var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src;
mediaTagEmbedder($('<media-tag src="' + src +
'" data-crypto-key="cryptpad:' + data.key + '"></media-tag>'), data);
}
});
$embedButton = common.createButton('mediatag', true).click(function () {
var cfg = {
types: ['file'],
where: ['root']
};
if ($embedButton.data('filter')) {
cfg.filter = $embedButton.data('filter');
}
common.openFilePicker(cfg);
});
}).appendTo(toolbar.$rightside).hide();
};
var setMediaTagEmbedder = function (mte, filter) {

View File

@@ -18,8 +18,7 @@ define([
var Cryptget;
var SFrameChannel;
var sframeChan;
var FilePicker;
var Share;
var SecureIframe;
var Messaging;
var Notifier;
var Utils = {
@@ -44,8 +43,7 @@ define([
'/bower_components/chainpad-crypto/crypto.js',
'/common/cryptget.js',
'/common/outer/worker-channel.js',
'/filepicker/main.js',
'/share/main.js',
'/secureiframe/main.js',
'/common/common-messaging.js',
'/common/common-notifier.js',
'/common/common-hash.js',
@@ -58,15 +56,14 @@ define([
'/common/test.js',
'/common/userObject.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
Crypto = Utils.Crypto = _Crypto;
Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel;
FilePicker = _FilePicker;
Share = _Share;
SecureIframe = _SecureIframe;
Messaging = _Messaging;
Notifier = _Notifier;
Utils.Hash = _Hash;
@@ -491,7 +488,7 @@ define([
// Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) {
var addCommonRpc = function (sframeChan, safe) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) {
cb({error: err, response: response});
@@ -598,6 +595,12 @@ define([
}
if (data.href) { href = data.href; }
Cryptpad.getPadAttribute(data.key, function (e, data) {
if (!safe && data) {
// Remove unsafe data for the unsafe iframe
delete data.href;
delete data.roHref;
delete data.password;
}
cb({
error: e,
data: data
@@ -985,81 +988,63 @@ define([
onFileUpload(sframeChan, data, cb);
});
// File picker
var FP = {};
var initFilePicker = function (cfg) {
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// Secure modal
var SecureModal = {};
// Create or display the iframe and modal
var initSecureModal = function (type, cfg, cb) {
cfg.modal = type;
SecureModal.cb = cb;
// cfg.hidden means pre-loading the iframe while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!FP.$iframe) {
if (!SecureModal.$iframe) {
var config = {};
config.onFilePicked = function (data) {
sframeChan.event('EV_FILE_PICKED', data);
config.onAction = function (data) {
if (typeof(SecureModal.cb) !== "function") { return; }
SecureModal.cb(data);
SecureModal.$iframe.hide();
};
config.onClose = function () {
FP.$iframe.hide();
SecureModal.$iframe.hide();
};
config.data = {
hashes: hashes,
password: password,
isTemplate: isTemplate
};
config.onFileUpload = onFileUpload;
config.types = cfg;
config.addCommonRpc = addCommonRpc;
config.modules = {
Cryptpad: Cryptpad,
SFrameChannel: SFrameChannel,
Utils: Utils
};
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body'));
FP.picker = FilePicker.create(config);
SecureModal.$iframe = $('<iframe>', {id: 'sbox-secure-iframe'}).appendTo($('body'));
SecureModal.modal = SecureIframe.create(config);
} else if (!cfg.hidden) {
FP.$iframe.show();
FP.picker.refresh(cfg);
}
if (cfg.hidden) {
FP.$iframe.hide();
return;
}
FP.$iframe.focus();
};
sframeChan.on('EV_FILE_PICKER_OPEN', function (data) {
initFilePicker(data);
});
// Share modal
var ShareModal = {};
var initShareModal = function (cfg) {
cfg.hashes = hashes;
cfg.password = password;
cfg.isTemplate = isTemplate;
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!ShareModal.$iframe) {
var config = {};
config.onShareAction = function (data) {
sframeChan.event('EV_SHARE_ACTION', data);
};
config.onClose = function () {
ShareModal.$iframe.hide();
};
config.data = cfg;
config.addCommonRpc = addCommonRpc;
config.modules = {
Cryptpad: Cryptpad,
SFrameChannel: SFrameChannel,
Utils: Utils
};
ShareModal.$iframe = $('<iframe>', {id: 'sbox-share-iframe'}).appendTo($('body'));
ShareModal.modal = Share.create(config);
} else if (!cfg.hidden) {
ShareModal.modal.refresh(cfg, function () {
ShareModal.$iframe.show();
SecureModal.modal.refresh(cfg, function () {
SecureModal.$iframe.show();
});
}
if (cfg.hidden) {
ShareModal.$iframe.hide();
SecureModal.$iframe.hide();
return;
}
ShareModal.$iframe.focus();
SecureModal.$iframe.focus();
};
sframeChan.on('Q_FILE_PICKER_OPEN', function (data, cb) {
initSecureModal('filepicker', data || {}, cb);
});
sframeChan.on('EV_PROPERTIES_OPEN', function (data) {
initSecureModal('properties', data || {}, null);
});
sframeChan.on('EV_ACCESS_OPEN', function (data) {
initSecureModal('access', data || {}, null);
});
sframeChan.on('EV_SHARE_OPEN', function (data) {
initShareModal(data || {});
initSecureModal('share', data || {}, null);
});
sframeChan.on('Q_TEMPLATE_USE', function (data, cb) {
@@ -1275,6 +1260,9 @@ define([
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// Try to get the owner's mailbox from the pad metadata first.
@@ -1334,6 +1322,9 @@ define([
var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// If we already have metadata, use it, otherwise, try to get it

View File

@@ -89,7 +89,6 @@ define([
window.CryptPad_UIElements = UIElements;
window.CryptPad_common = funcs;
funcs.createUserAdminMenu = callWithCommon(UIElements.createUserAdminMenu);
funcs.initFilePicker = callWithCommon(UIElements.initFilePicker);
funcs.openFilePicker = callWithCommon(UIElements.openFilePicker);
funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker);
funcs.displayMediatagImage = callWithCommon(MT.displayMediatagImage);

View File

@@ -350,7 +350,7 @@
"fm_info_root": "Creeu aquí tantes carpetes imbrincades com vulgueu per ordenar els vostres fitxers.",
"fm_info_unsorted": "Conté tots els fitxers que heu visitat i que encara no estan ordenats, a \"Documents\" o desplaçats a la \"Paperera\".",
"fm_info_template": "Conté tots els documents desats com plantilles i que podeu reutilitzar quan vulgueu crear un nou document.",
"fm_info_recent": "Llista els documents modificats o oberts recentment.",
"fm_info_recent": "Aquests documents s'han modificat o obert darrerament, per vós o per alguna persona col·laboradora.",
"fm_info_trash": "Buideu la paperera per alliberar espai al vostre CryptDrive.",
"fm_info_allFiles": "Conté tots els fitxers de \"Documents\", \"Desordenats\" i \"Paperera\". No podeu desplaçar o suprimir fitxers des d'aquí.",
"fm_info_anonymous": "No heu iniciat la sessió, per tant, els vostres documents caducaran d'aquí a 3 mesos (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">saber-ne més</a>). Es desen al vostre navegador, per tant, si netegeu el vostre historial podríeu perdre'ls.<br><a href=\"/register/\">Registreu-vos</a> o <a href=\"/login/\">Inicieu la sessió</a> per mantenir-los accessibles.<br>",
@@ -515,7 +515,7 @@
"settings_pinningError": "Alguna cosa no ha funcionat correctament",
"settings_usageAmount": "Els vostres documents fixats ocupen {0} MB",
"settings_logoutEverywhereButton": "Tanca la sessió",
"settings_logoutEverywhereTitle": "Tanca la sessió arreu",
"settings_logoutEverywhereTitle": "Tanca les sessions remotes",
"settings_logoutEverywhere": "Tanca totes les altres sessions",
"settings_logoutEverywhereConfirm": "De debò? Haureu de tornar a iniciar la vostra sessió a tots els dispositius.",
"settings_driveDuplicateTitle": "Documents propis duplicats",
@@ -573,7 +573,7 @@
"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_tooLarge": "Aquest fitxer supera la mida màxima permesa pel vostre compte",
"upload_tooLargeBrief": "El fitxer és massa gran",
"upload_choose": "Trieu un fitxer",
"upload_pending": "Pendent",
@@ -619,5 +619,20 @@
"download_resourceNotAvailable": "El recurs sol·licitat no estava disponible... Premeu Esc per continuar.",
"about_contributors": "Col·laboracions clau",
"about_core": "Desenvolupament principal",
"about_intro": "CryptPad s'ha creat dins l'Equip de Recerca de <a href=\"http://xwiki.com\">XWiki SAS</a>, una petita empresa de París, França i Iasi, Romania. Hi ha 3 membres de l'equip central treballant amb CryptPad més una quantitat de persones col·laboradores, dins i fora d'XWiki SAS."
"about_intro": "CryptPad s'ha creat dins l'Equip de Recerca de <a href=\"http://xwiki.com\">XWiki SAS</a>, una petita empresa de París, França i Iasi, Romania. Hi ha 3 membres de l'equip central treballant amb CryptPad més una quantitat de persones col·laboradores, dins i fora d'XWiki SAS.",
"main_catch_phrase": "El Núvol Coneixement Zero",
"main_footerText": "Amb Cryptpad, podeu crear documents col·laboratius per prendre notes i posar en ordre idees de forma conjunta de forma ràpida.",
"footer_applications": "Aplicacions",
"footer_contact": "Contacte",
"footer_aboutUs": "Sobre nosaltres",
"about": "Sobre",
"contact": "Contacte",
"blog": "Bloc",
"topbar_whatIsCryptpad": "Què és CryptPad",
"whatis_collaboration": "Col·laboració fàcil i ràpida",
"whatis_title": "Què és CryptPad",
"terms": "Condicions d'ús",
"main_info": "<h2>Col·laboreu amb Confiança</h2>\nFeu créixer les vostres idees conjuntament amb documents compartits mentre la tecnologia <strong>Coneixement Zero</strong> assegura la vostra privacitat; <strong>fins i tot per nosaltres</strong>.",
"whatis_collaboration_p1": "Amb CryptPad, podeu crear de forma ràpida, documents col·laboratius per prendre notes i posar en ordre idees conjuntament. Quan us registreu i inicieu la vostra sessió, teniu la capacitat de carregar fitxers i un CryptDrive on podeu organitzar tots els vostres documents. Com a persona registrada disposeu de 50MB d'espai gratuït.",
"privacy": "Privacitat"
}