Merge branch 'staging' into ooBuild
This commit is contained in:
@@ -60,6 +60,23 @@ var factory = function (Util, Crypto, Nacl) {
|
||||
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
|
||||
}
|
||||
};
|
||||
|
||||
Hash.getHiddenHashFromKeys = function (type, secret, opts) {
|
||||
opts = opts || {};
|
||||
var canEdit = (secret.keys && secret.keys.editKeyStr) || secret.key;
|
||||
var mode = (!opts.view && canEdit) ? '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 version = secret.version;
|
||||
var data = secret.keys;
|
||||
@@ -160,12 +177,28 @@ Version 1
|
||||
};
|
||||
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
|
||||
if (!hash) { return; }
|
||||
var options;
|
||||
var options = [];
|
||||
var parsed = {};
|
||||
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) {
|
||||
parsed.type = 'pad';
|
||||
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
|
||||
// Old hash
|
||||
parsed.channel = hash.slice(0, 32);
|
||||
@@ -173,6 +206,18 @@ Version 1
|
||||
parsed.version = 0;
|
||||
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
|
||||
parsed.version = 1;
|
||||
parsed.mode = hashArr[2];
|
||||
@@ -180,18 +225,8 @@ Version 1
|
||||
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
|
||||
|
||||
options = hashArr.slice(5);
|
||||
parsed.present = options.indexOf('present') !== -1;
|
||||
parsed.embed = options.indexOf('embed') !== -1;
|
||||
parsed.ownerKey = getOwnerKey(options);
|
||||
addOptions();
|
||||
|
||||
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;
|
||||
}
|
||||
if (hashArr[1] && hashArr[1] === '2') { // Version 2
|
||||
@@ -201,20 +236,19 @@ Version 1
|
||||
parsed.key = hashArr[4];
|
||||
|
||||
options = hashArr.slice(5);
|
||||
parsed.password = options.indexOf('p') !== -1;
|
||||
parsed.present = options.indexOf('present') !== -1;
|
||||
parsed.embed = options.indexOf('embed') !== -1;
|
||||
parsed.ownerKey = getOwnerKey(options);
|
||||
addOptions();
|
||||
|
||||
return parsed;
|
||||
}
|
||||
if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
|
||||
parsed.version = 3;
|
||||
parsed.app = hashArr[2];
|
||||
parsed.mode = hashArr[3];
|
||||
parsed.channel = hashArr[4];
|
||||
|
||||
options = hashArr.slice(5);
|
||||
addOptions();
|
||||
|
||||
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) { hash += 'p/'; }
|
||||
if (opts.embed) { hash += 'embed/'; }
|
||||
if (opts.present) { hash += 'present/'; }
|
||||
return hash;
|
||||
};
|
||||
return parsed;
|
||||
}
|
||||
return parsed;
|
||||
@@ -222,34 +256,54 @@ Version 1
|
||||
parsed.getHash = function () { return hashArr.join('/'); };
|
||||
if (['media', 'file'].indexOf(type) !== -1) {
|
||||
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') {
|
||||
parsed.version = 1;
|
||||
parsed.channel = hashArr[2].replace(/-/g, '/');
|
||||
parsed.key = hashArr[3].replace(/-/g, '/');
|
||||
options = hashArr.slice(4);
|
||||
parsed.ownerKey = getOwnerKey(options);
|
||||
addOptions();
|
||||
return parsed;
|
||||
}
|
||||
|
||||
if (hashArr[1] && hashArr[1] === '2') { // Version 2
|
||||
parsed.version = 2;
|
||||
parsed.app = hashArr[2];
|
||||
parsed.key = hashArr[3];
|
||||
|
||||
options = hashArr.slice(4);
|
||||
parsed.password = options.indexOf('p') !== -1;
|
||||
parsed.present = options.indexOf('present') !== -1;
|
||||
parsed.embed = options.indexOf('embed') !== -1;
|
||||
parsed.ownerKey = getOwnerKey(options);
|
||||
addOptions();
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
if (hashArr[1] && hashArr[1] === '3') { // Version 3: hidden hash
|
||||
parsed.version = 3;
|
||||
parsed.app = hashArr[2];
|
||||
parsed.channel = hashArr[3];
|
||||
|
||||
options = hashArr.slice(4);
|
||||
addOptions();
|
||||
|
||||
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) { hash += 'p/'; }
|
||||
if (opts.embed) { hash += 'embed/'; }
|
||||
if (opts.present) { hash += 'present/'; }
|
||||
return hash;
|
||||
};
|
||||
return parsed;
|
||||
}
|
||||
return parsed;
|
||||
@@ -303,6 +357,10 @@ Version 1
|
||||
url += '#' + hash;
|
||||
return url;
|
||||
};
|
||||
ret.getOptions = function () {
|
||||
if (!ret.hashData || !ret.hashData.getOptions) { return {}; }
|
||||
return ret.hashData.getOptions();
|
||||
};
|
||||
|
||||
if (!/^https*:\/\//.test(href)) {
|
||||
idx = href.indexOf('/#');
|
||||
@@ -325,6 +383,14 @@ Version 1
|
||||
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) {
|
||||
if (!href) { return; }
|
||||
if (href.indexOf('#') === -1) { return; }
|
||||
@@ -345,7 +411,7 @@ Version 1
|
||||
secret.version = 2;
|
||||
secret.type = type;
|
||||
};
|
||||
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
|
||||
if (!secretHash) {
|
||||
generate();
|
||||
return secret;
|
||||
} else {
|
||||
@@ -355,12 +421,7 @@ Version 1
|
||||
if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); }
|
||||
parsed = parseTypeHash(type, 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) {
|
||||
generate();
|
||||
return secret;
|
||||
@@ -496,8 +557,8 @@ Version 1
|
||||
if (typeof(parsed.hashData.version) === "undefined") { return; }
|
||||
// pads and files should have a base64 (or hex) key
|
||||
if (parsed.hashData.type === 'pad' || parsed.hashData.type === 'file') {
|
||||
if (!parsed.hashData.key) { return; }
|
||||
if (!/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; }
|
||||
if (!parsed.hashData.key && !parsed.hashData.channel) { return; }
|
||||
if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; }
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -70,6 +70,7 @@ define([
|
||||
if (typeof(yes) === 'function') { yes(e); }
|
||||
break;
|
||||
}
|
||||
$(el || window).off('keydown', handler);
|
||||
};
|
||||
|
||||
$(el || window).keydown(handler);
|
||||
@@ -197,7 +198,7 @@ define([
|
||||
frame.closeModal = function (cb) {
|
||||
$frame.fadeOut(150, function () {
|
||||
$frame.detach();
|
||||
cb();
|
||||
if (typeof(cb) === "function") { cb(); }
|
||||
});
|
||||
};
|
||||
return $frame.click(function (e) {
|
||||
@@ -217,14 +218,15 @@ define([
|
||||
var titles = [];
|
||||
var active = 0;
|
||||
tabs.forEach(function (tab, i) {
|
||||
if (!tab.content || !tab.title) { return; }
|
||||
if (!(tab.content || tab.disabled) || !tab.title) { return; }
|
||||
var content = h('div.alertify-tabs-content', tab.content);
|
||||
var title = h('span.alertify-tabs-title', tab.title);
|
||||
var title = h('span.alertify-tabs-title'+ (tab.disabled ? '.disabled' : ''), tab.title);
|
||||
if (tab.icon) {
|
||||
var icon = h('i', {class: tab.icon});
|
||||
$(title).prepend(' ').prepend(icon);
|
||||
}
|
||||
$(title).click(function () {
|
||||
if (tab.disabled) { return; }
|
||||
var old = tabs[active];
|
||||
if (old.onHide) { old.onHide(); }
|
||||
titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); });
|
||||
@@ -238,7 +240,7 @@ define([
|
||||
});
|
||||
titles.push(title);
|
||||
contents.push(content);
|
||||
if (tab.active) { active = i; }
|
||||
if (tab.active && !tab.disabled) { active = i; }
|
||||
});
|
||||
if (contents.length) {
|
||||
$(contents[active]).addClass('alertify-tabs-content-active');
|
||||
@@ -384,7 +386,7 @@ define([
|
||||
buttons.forEach(function (b) {
|
||||
if (!b.name || !b.onClick) { return; }
|
||||
var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name);
|
||||
$(button).click(function () {
|
||||
var todo = function () {
|
||||
var noClose = b.onClick();
|
||||
if (noClose) { return; }
|
||||
var $modal = $(button).parents('.alertify').first();
|
||||
@@ -395,7 +397,17 @@ define([
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
if (b.confirm) {
|
||||
UI.confirmButton(button, {
|
||||
classes: 'danger',
|
||||
divClasses: 'left'
|
||||
}, todo);
|
||||
} else {
|
||||
$(button).click(function () {
|
||||
todo();
|
||||
});
|
||||
}
|
||||
if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); }
|
||||
navs.push(button);
|
||||
});
|
||||
@@ -483,7 +495,7 @@ define([
|
||||
stopListening(listener);
|
||||
cb();
|
||||
});
|
||||
listener = listenForKeys(close, close);
|
||||
listener = listenForKeys(close, close, frame);
|
||||
var $ok = $(ok).click(close);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
@@ -491,6 +503,11 @@ define([
|
||||
$ok.focus();
|
||||
Notifier.notify();
|
||||
});
|
||||
|
||||
return {
|
||||
element: frame,
|
||||
delete: close
|
||||
};
|
||||
};
|
||||
|
||||
UI.prompt = function (msg, def, cb, opt, force) {
|
||||
@@ -582,7 +599,7 @@ define([
|
||||
$ok.click();
|
||||
}, function () {
|
||||
$cancel.click();
|
||||
}, ok);
|
||||
}, frame);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
setTimeout(function () {
|
||||
@@ -593,6 +610,70 @@ define([
|
||||
}
|
||||
});
|
||||
};
|
||||
UI.confirmButton = function (originalBtn, config, _cb) {
|
||||
config = config || {};
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var classes = 'btn ' + (config.classes || 'btn-primary');
|
||||
|
||||
var button = h('button', {
|
||||
"class": classes,
|
||||
title: config.title || ''
|
||||
}, Messages.areYouSure);
|
||||
var $button = $(button);
|
||||
|
||||
var div = h('div', {
|
||||
"class": config.classes || ''
|
||||
});
|
||||
var timer = h('div.cp-button-timer', div);
|
||||
|
||||
var content = h('div.cp-button-confirm', [
|
||||
button,
|
||||
timer
|
||||
]);
|
||||
if (config.divClasses) {
|
||||
$(content).addClass(config.divClasses);
|
||||
}
|
||||
|
||||
var to;
|
||||
|
||||
var done = function (res) {
|
||||
if (res) { cb(res); }
|
||||
clearTimeout(to);
|
||||
$(content).detach();
|
||||
$(originalBtn).show();
|
||||
};
|
||||
|
||||
$button.click(function () {
|
||||
done(true);
|
||||
});
|
||||
|
||||
var TIMEOUT = 3000;
|
||||
var INTERVAL = 10;
|
||||
var i = 1;
|
||||
|
||||
var todo = function () {
|
||||
var p = 100 * ((TIMEOUT - (i * INTERVAL)) / TIMEOUT);
|
||||
if (i++ * INTERVAL >= TIMEOUT) {
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
$(div).css('width', p+'%');
|
||||
to = setTimeout(todo, INTERVAL);
|
||||
};
|
||||
|
||||
$(originalBtn).addClass('cp-button-confirm-placeholder').click(function () {
|
||||
i = 1;
|
||||
to = setTimeout(todo, INTERVAL);
|
||||
$(originalBtn).hide().after(content);
|
||||
});
|
||||
|
||||
return {
|
||||
reset: function () {
|
||||
done(false);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
UI.proposal = function (content, cb) {
|
||||
var buttons = [{
|
||||
@@ -1050,39 +1131,36 @@ define([
|
||||
return radio;
|
||||
};
|
||||
|
||||
var corner = {
|
||||
queue: [],
|
||||
state: false
|
||||
};
|
||||
UI.cornerPopup = function (text, actions, footer, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
var minimize = h('div.cp-corner-minimize.fa.fa-window-minimize');
|
||||
var maximize = h('div.cp-corner-maximize.fa.fa-window-maximize');
|
||||
var dontShowAgain = h('div.cp-corner-dontshow', [
|
||||
h('span.fa.fa-times'),
|
||||
Messages.dontShowAgain
|
||||
]);
|
||||
|
||||
var popup = h('div.cp-corner-container', [
|
||||
minimize,
|
||||
maximize,
|
||||
h('div.cp-corner-filler', { style: "width:110px;" }),
|
||||
h('div.cp-corner-filler', { style: "width:80px;" }),
|
||||
h('div.cp-corner-filler', { style: "width:60px;" }),
|
||||
h('div.cp-corner-filler', { style: "width:40px;" }),
|
||||
h('div.cp-corner-filler', { style: "width:20px;" }),
|
||||
setHTML(h('div.cp-corner-text'), text),
|
||||
h('div.cp-corner-actions', actions),
|
||||
setHTML(h('div.cp-corner-footer'), footer)
|
||||
setHTML(h('div.cp-corner-footer'), footer),
|
||||
opts.dontShowAgain ? dontShowAgain : undefined
|
||||
]);
|
||||
|
||||
var $popup = $(popup);
|
||||
|
||||
$(minimize).click(function () {
|
||||
$popup.addClass('cp-minimized');
|
||||
});
|
||||
$(maximize).click(function () {
|
||||
$popup.removeClass('cp-minimized');
|
||||
});
|
||||
|
||||
if (opts.hidden) {
|
||||
$popup.addClass('cp-minimized');
|
||||
}
|
||||
if (opts.big) {
|
||||
$popup.addClass('cp-corner-big');
|
||||
}
|
||||
if (opts.alt) {
|
||||
$popup.addClass('cp-corner-alt');
|
||||
}
|
||||
|
||||
var hide = function () {
|
||||
$popup.hide();
|
||||
@@ -1092,9 +1170,35 @@ define([
|
||||
};
|
||||
var deletePopup = function () {
|
||||
$popup.remove();
|
||||
if (!corner.queue.length) {
|
||||
// Make sure no other popup is displayed in the next 5s
|
||||
setTimeout(function () {
|
||||
if (corner.queue.length) {
|
||||
$('body').append(corner.queue.pop());
|
||||
return;
|
||||
}
|
||||
corner.state = false;
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
setTimeout(function () {
|
||||
$('body').append(corner.queue.pop());
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
$('body').append(popup);
|
||||
$(dontShowAgain).click(function () {
|
||||
deletePopup();
|
||||
if (typeof(opts.dontShowAgain) === "function") {
|
||||
opts.dontShowAgain();
|
||||
}
|
||||
});
|
||||
|
||||
if (corner.state) {
|
||||
corner.queue.push(popup);
|
||||
} else {
|
||||
corner.state = true;
|
||||
$('body').append(popup);
|
||||
}
|
||||
|
||||
return {
|
||||
popup: popup,
|
||||
@@ -1104,5 +1208,49 @@ 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 state = false;
|
||||
var to;
|
||||
|
||||
var spin = function () {
|
||||
clearTimeout(to);
|
||||
state = true;
|
||||
$ok.hide();
|
||||
$spinner.show();
|
||||
};
|
||||
var hide = function () {
|
||||
clearTimeout(to);
|
||||
state = false;
|
||||
$ok.hide();
|
||||
$spinner.hide();
|
||||
};
|
||||
var done = function () {
|
||||
clearTimeout(to);
|
||||
state = false;
|
||||
$ok.show();
|
||||
$spinner.hide();
|
||||
to = setTimeout(function () {
|
||||
$ok.hide();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
if ($container && $container.append) {
|
||||
$container.append($ok);
|
||||
$container.append($spinner);
|
||||
}
|
||||
|
||||
return {
|
||||
getState: function () { return state; },
|
||||
ok: $ok[0],
|
||||
spinner: $spinner[0],
|
||||
spin: spin,
|
||||
hide: hide,
|
||||
done: done
|
||||
};
|
||||
};
|
||||
|
||||
return UI;
|
||||
});
|
||||
|
||||
@@ -53,10 +53,18 @@ define([
|
||||
return list;
|
||||
};
|
||||
|
||||
Msg.declineFriendRequest = function (store, data, cb) {
|
||||
store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {}, {
|
||||
channel: data.notifications,
|
||||
curvePublic: data.curvePublic
|
||||
}, function (obj) {
|
||||
cb(obj);
|
||||
});
|
||||
};
|
||||
Msg.acceptFriendRequest = function (store, data, cb) {
|
||||
var friend = getFriend(store.proxy, data.curvePublic) || {};
|
||||
var myData = createData(store.proxy, friend.channel || data.channel);
|
||||
store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', myData, {
|
||||
store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', { user: myData }, {
|
||||
channel: data.notifications,
|
||||
curvePublic: data.curvePublic
|
||||
}, function (obj) {
|
||||
@@ -110,7 +118,7 @@ define([
|
||||
var proxy = store.proxy;
|
||||
var friend = proxy.friends[curvePublic];
|
||||
if (!friend) { return void cb({error: 'ENOENT'}); }
|
||||
if (!friend.notifications || !friend.channel) { return void cb({error: 'EINVAL'}); }
|
||||
if (!friend.notifications) { return void cb({error: 'EINVAL'}); }
|
||||
|
||||
store.mailbox.sendTo('UNFRIEND', {
|
||||
curvePublic: proxy.curvePublic
|
||||
|
||||
@@ -14,7 +14,7 @@ define([
|
||||
'/customize/application_config.js',
|
||||
'/customize/pages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/invitation.js',
|
||||
'/common/inner/invitation.js',
|
||||
|
||||
'css!/customize/fonts/cptools/style.css',
|
||||
'/bower_components/croppie/croppie.min.js',
|
||||
@@ -30,6 +30,13 @@ define([
|
||||
});
|
||||
}
|
||||
|
||||
UIElements.prettySize = function (bytes) {
|
||||
var kB = Util.bytesToKilobytes(bytes);
|
||||
if (kB < 1024) { return kB + Messages.KB; }
|
||||
var mB = Util.bytesToMegabytes(bytes);
|
||||
return mB + Messages.MB;
|
||||
};
|
||||
|
||||
UIElements.updateTags = function (common, href) {
|
||||
var existing, tags;
|
||||
NThen(function(waitFor) {
|
||||
@@ -56,6 +63,21 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var dcAlert;
|
||||
UIElements.disconnectAlert = function () {
|
||||
if (dcAlert && $(dcAlert.element).length) { return; }
|
||||
dcAlert = UI.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
UIElements.reconnectAlert = function () {
|
||||
if (!dcAlert) { return; }
|
||||
if (!dcAlert.delete) {
|
||||
dcAlert = undefined;
|
||||
return;
|
||||
}
|
||||
dcAlert.delete();
|
||||
dcAlert = undefined;
|
||||
};
|
||||
|
||||
var importContent = function (type, f, cfg) {
|
||||
return function () {
|
||||
var $files = $('<input>', {type:"file"});
|
||||
@@ -77,7 +99,7 @@ define([
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
var getPropertiesData = function (common, cb) {
|
||||
var data = {};
|
||||
NThen(function (waitFor) {
|
||||
@@ -105,6 +127,43 @@ define([
|
||||
cb(void 0, data);
|
||||
});
|
||||
};
|
||||
*/
|
||||
var getPropertiesData = function (common, opts, cb) {
|
||||
opts = opts || {};
|
||||
var data = {};
|
||||
NThen(function (waitFor) {
|
||||
var base = common.getMetadataMgr().getPrivateData().origin;
|
||||
common.getPadAttribute('', waitFor(function (err, val) {
|
||||
if (err || !val) {
|
||||
waitFor.abort();
|
||||
return void cb(err || 'EEMPTY');
|
||||
}
|
||||
if (!val.fileType) {
|
||||
delete val.owners;
|
||||
delete val.expire;
|
||||
}
|
||||
Util.extend(data, val);
|
||||
if (data.href) { data.href = base + data.href; }
|
||||
if (data.roHref) { data.roHref = base + data.roHref; }
|
||||
}), opts.href);
|
||||
|
||||
// If this is a file, don't try to look for metadata
|
||||
if (opts.channel && opts.channel.length > 34) { return; }
|
||||
common.getPadMetadata({
|
||||
channel: opts.channel // optional, fallback to current pad
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { return; }
|
||||
data.owners = obj.owners;
|
||||
data.expire = obj.expire;
|
||||
data.pending_owners = obj.pending_owners;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
cb(void 0, data);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
var createOwnerModal = function (common, data) {
|
||||
var friends = common.getFriends(true);
|
||||
var sframeChan = common.getSframeChannel();
|
||||
@@ -212,15 +271,7 @@ define([
|
||||
common.mailbox.sendTo("RM_OWNER", {
|
||||
channel: channel,
|
||||
title: data.title,
|
||||
pending: pending,
|
||||
user: {
|
||||
displayName: user.name,
|
||||
avatar: user.avatar,
|
||||
profile: user.profile,
|
||||
notifications: user.notifications,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: priv.edPublic
|
||||
}
|
||||
pending: pending
|
||||
}, {
|
||||
channel: friend.notifications,
|
||||
curvePublic: friend.curvePublic
|
||||
@@ -363,15 +414,7 @@ define([
|
||||
channel: channel,
|
||||
href: data.href,
|
||||
password: data.password,
|
||||
title: data.title,
|
||||
user: {
|
||||
displayName: user.name,
|
||||
avatar: user.avatar,
|
||||
profile: user.profile,
|
||||
notifications: user.notifications,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: priv.edPublic
|
||||
}
|
||||
title: data.title
|
||||
}, {
|
||||
channel: friend.notifications,
|
||||
curvePublic: friend.curvePublic
|
||||
@@ -419,8 +462,8 @@ define([
|
||||
var link = h('div.cp-share-columns', [
|
||||
div1,
|
||||
div2
|
||||
/*drawRemove()[0],
|
||||
drawAdd()[0]*/
|
||||
// drawRemove()[0],
|
||||
//drawAdd()[0]
|
||||
]);
|
||||
var linkButtons = [{
|
||||
className: 'cancel',
|
||||
@@ -430,6 +473,8 @@ define([
|
||||
}];
|
||||
return UI.dialog.customModal(link, {buttons: linkButtons});
|
||||
};
|
||||
*/
|
||||
/*
|
||||
var getRightsProperties = function (common, data, cb) {
|
||||
var $div = $('<div>');
|
||||
if (!data) { return void cb(void 0, $div); }
|
||||
@@ -449,7 +494,7 @@ define([
|
||||
var team = priv.teams[id] || {};
|
||||
if (team.viewer) { return; }
|
||||
if (data.owners.indexOf(team.edPublic) === -1) { return; }
|
||||
owned = id;
|
||||
owned = Number(id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -548,21 +593,27 @@ define([
|
||||
|
||||
if (!data.noPassword) {
|
||||
var hasPassword = data.password;
|
||||
var $pwLabel = $('<label>', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
|
||||
.hide().appendTo($d);
|
||||
var password = UI.passwordInput({
|
||||
id: 'cp-app-prop-password',
|
||||
readonly: 'readonly'
|
||||
});
|
||||
var $password = $(password).hide();
|
||||
var $pwInput = $password.find('.cp-password-input');
|
||||
$pwInput.val(data.password).click(function () {
|
||||
$pwInput[0].select();
|
||||
});
|
||||
$d.append(password);
|
||||
|
||||
if (hasPassword) {
|
||||
$('<label>', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
|
||||
.appendTo($d);
|
||||
var password = UI.passwordInput({
|
||||
id: 'cp-app-prop-password',
|
||||
readonly: 'readonly'
|
||||
});
|
||||
var $pwInput = $(password).find('.cp-password-input');
|
||||
$pwInput.val(data.password).click(function () {
|
||||
$pwInput[0].select();
|
||||
});
|
||||
$d.append(password);
|
||||
$pwLabel.show();
|
||||
$password.css('display', 'flex');
|
||||
}
|
||||
|
||||
if (!data.noEditPassword && owned) { // FIXME SHEET fix password change for sheets
|
||||
// In the properties, we should have the edit href if we know it.
|
||||
// We should know it because the pad is stored, but it's better to check...
|
||||
if (!data.noEditPassword && owned && data.href) { // FIXME SHEET fix password change for sheets
|
||||
var sframeChan = common.getSframeChannel();
|
||||
|
||||
var isOO = parsed.type === 'sheet';
|
||||
@@ -622,7 +673,7 @@ define([
|
||||
|
||||
sframeChan.query(q, {
|
||||
teamId: typeof(owned) !== "boolean" ? owned : undefined,
|
||||
href: data.href || data.roHref,
|
||||
href: data.href,
|
||||
password: newPass
|
||||
}, function (err, data) {
|
||||
$(passwordOk).text(Messages.properties_changePasswordButton);
|
||||
@@ -632,24 +683,41 @@ define([
|
||||
return void UI.alert(Messages.properties_passwordError);
|
||||
}
|
||||
UI.findOKButton().click();
|
||||
if (isFile) {
|
||||
onProgress.stop();
|
||||
|
||||
$pwLabel.show();
|
||||
$password.css('display', 'flex');
|
||||
$pwInput.val(newPass);
|
||||
|
||||
// If the current document is a file or if we're changing the password from a drive,
|
||||
// we don't have to reload the page at the end.
|
||||
// Tell the user the password change was successful and abort
|
||||
if (isFile || priv.app !== parsed.type) {
|
||||
if (onProgress && onProgress.stop) { onProgress.stop(); }
|
||||
$(passwordOk).text(Messages.properties_changePasswordButton);
|
||||
var alertMsg = data.warning ? Messages.properties_passwordWarningFile
|
||||
: Messages.properties_passwordSuccessFile;
|
||||
return void UI.alert(alertMsg, undefined, {force: true});
|
||||
}
|
||||
// If we didn't have a password, we have to add the /p/
|
||||
// If we had a password and we changed it to a new one, we just have to reload
|
||||
// If we had a password and we removed it, we have to remove the /p/
|
||||
|
||||
// Pad password changed: update the href
|
||||
// Use hidden hash if needed (we're an owner of this pad so we know it is stored)
|
||||
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
|
||||
var href = (priv.readOnly && data.roHref) ? data.roHref : data.href;
|
||||
if (useUnsafe === false) {
|
||||
var newParsed = Hash.parsePadUrl(href);
|
||||
var newSecret = Hash.getSecrets(newParsed.type, newParsed.hash, newPass);
|
||||
var newHash = Hash.getHiddenHashFromKeys(parsed.type, newSecret, {});
|
||||
href = Hash.hashToHref(newHash, parsed.type);
|
||||
}
|
||||
|
||||
if (data.warning) {
|
||||
return void UI.alert(Messages.properties_passwordWarning, function () {
|
||||
common.gotoURL(hasPassword && newPass ? undefined : (data.href || data.roHref));
|
||||
common.gotoURL(href);
|
||||
}, {force: true});
|
||||
}
|
||||
return void UI.alert(Messages.properties_passwordSuccess, function () {
|
||||
if (!isSharedFolder) {
|
||||
common.gotoURL(hasPassword && newPass ? undefined : (data.href || data.roHref));
|
||||
common.gotoURL(href);
|
||||
}
|
||||
}, {force: true});
|
||||
});
|
||||
@@ -678,10 +746,16 @@ define([
|
||||
|
||||
cb(void 0, $div);
|
||||
};
|
||||
var getPadProperties = function (common, data, cb) {
|
||||
*/
|
||||
|
||||
var getPadProperties = function (common, data, opts, cb) {
|
||||
opts = opts || {};
|
||||
var $d = $('<div>');
|
||||
if (!data) { return void cb(void 0, $d); }
|
||||
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var edPublic = priv.edPublic;
|
||||
|
||||
if (data.href) {
|
||||
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
|
||||
$d.append(UI.dialog.selectable(data.href, {
|
||||
@@ -689,7 +763,7 @@ define([
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.roHref) {
|
||||
if (data.roHref && !opts.noReadOnly) {
|
||||
$('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
|
||||
$d.append(UI.dialog.selectable(data.roHref, {
|
||||
id: 'cp-app-prop-rolink',
|
||||
@@ -708,13 +782,30 @@ define([
|
||||
$d.append(h('div.cp-app-prop', [Messages.fm_lastAccess, h('br'), h('span.cp-app-prop-content', new Date(data.atime).toLocaleString())]));
|
||||
}
|
||||
|
||||
var owned = false;
|
||||
if (common.isLoggedIn()) {
|
||||
if (Array.isArray(data.owners)) {
|
||||
if (data.owners.indexOf(edPublic) !== -1) {
|
||||
owned = true;
|
||||
} else {
|
||||
Object.keys(priv.teams || {}).some(function (id) {
|
||||
var team = priv.teams[id] || {};
|
||||
if (team.viewer) { return; }
|
||||
if (data.owners.indexOf(team.edPublic) === -1) { return; }
|
||||
owned = Number(id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
// check the size of this file...
|
||||
var bytes = 0;
|
||||
var historyBytes;
|
||||
var chan = [data.channel];
|
||||
if (data.rtChannel) { chan.push(data.rtChannel); }
|
||||
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
|
||||
var history = common.makeUniversal('history');
|
||||
var trimChannels = [];
|
||||
NThen(function (waitFor) {
|
||||
var chan = [data.channel];
|
||||
if (data.rtChannel) { chan.push(data.rtChannel); }
|
||||
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
|
||||
chan.forEach(function (c) {
|
||||
common.getFileSize(c, waitFor(function (e, _bytes) {
|
||||
if (e) {
|
||||
@@ -724,20 +815,83 @@ define([
|
||||
bytes += _bytes;
|
||||
}));
|
||||
});
|
||||
|
||||
if (!owned) { return; }
|
||||
history.execCommand('GET_HISTORY_SIZE', {
|
||||
pad: true,
|
||||
channels: chan.filter(function (c) { return c.length === 32; }),
|
||||
teamId: typeof(owned) === "number" && owned
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { return; }
|
||||
historyBytes = obj.size;
|
||||
trimChannels = obj.channels;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (bytes === 0) { return void cb(void 0, $d); }
|
||||
var KB = Util.bytesToKilobytes(bytes);
|
||||
var formatted = UIElements.prettySize(bytes);
|
||||
|
||||
var formatted = Messages._getKey('formattedKB', [KB]);
|
||||
$d.append(h('div.cp-app-prop', [Messages.upload_size, h('br'), h('span.cp-app-prop-content', formatted)]));
|
||||
if (!owned || !historyBytes || historyBytes > bytes || historyBytes < 0) {
|
||||
$d.append(h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('span.cp-app-prop-content', formatted)
|
||||
]));
|
||||
return void cb(void 0, $d);
|
||||
}
|
||||
|
||||
if (data.sharedFolder && false) {
|
||||
$('<label>', {'for': 'cp-app-prop-channel'}).text('Channel ID').appendTo($d);
|
||||
if (AppConfig.pinBugRecovery) { $d.append(h('p', AppConfig.pinBugRecovery)); }
|
||||
$d.append(UI.dialog.selectable(data.channel, {
|
||||
id: 'cp-app-prop-link',
|
||||
}));
|
||||
}
|
||||
|
||||
var p = Math.round((historyBytes / bytes) * 100);
|
||||
var historyPrettySize = UIElements.prettySize(historyBytes);
|
||||
var contentsPrettySize = UIElements.prettySize(bytes - historyBytes);
|
||||
var button;
|
||||
var spinner = UI.makeSpinner();
|
||||
var size = h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('div.cp-app-prop-size-container', [
|
||||
h('div.cp-app-prop-size-history', { style: 'width:'+p+'%;' })
|
||||
]),
|
||||
h('div.cp-app-prop-size-legend', [
|
||||
h('div.cp-app-prop-history-size', [
|
||||
h('span.cp-app-prop-history-size-color'),
|
||||
h('span.cp-app-prop-content', Messages._getKey('historyTrim_historySize', [historyPrettySize]))
|
||||
]),
|
||||
h('div.cp-app-prop-contents-size', [
|
||||
h('span.cp-app-prop-contents-size-color'),
|
||||
h('span.cp-app-prop-content', Messages._getKey('historyTrim_contentsSize', [contentsPrettySize]))
|
||||
]),
|
||||
]),
|
||||
button = h('button.btn.btn-danger-alt.no-margin', Messages.trimHistory_button),
|
||||
spinner.spinner
|
||||
]);
|
||||
$d.append(size);
|
||||
|
||||
var $button = $(button);
|
||||
UI.confirmButton(button, {
|
||||
classes: 'btn-danger'
|
||||
}, function () {
|
||||
$button.remove();
|
||||
spinner.spin();
|
||||
history.execCommand('TRIM_HISTORY', {
|
||||
pad: true,
|
||||
channels: trimChannels,
|
||||
teamId: typeof(owned) === "number" && owned
|
||||
}, function (obj) {
|
||||
spinner.hide();
|
||||
if (obj && obj.error) {
|
||||
$(size).append(h('div.alert.alert-danger', Messages.trimHistory_error));
|
||||
return;
|
||||
}
|
||||
$(size).remove();
|
||||
var formatted = UIElements.prettySize(bytes - historyBytes);
|
||||
$d.append(h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('span.cp-app-prop-content', formatted)
|
||||
]));
|
||||
$d.append(h('div.alert.alert-success', Messages.trimHistory_success));
|
||||
});
|
||||
});
|
||||
|
||||
cb(void 0, $d);
|
||||
});
|
||||
@@ -747,35 +901,42 @@ define([
|
||||
|
||||
|
||||
};
|
||||
UIElements.getProperties = function (common, data, cb) {
|
||||
var c1;
|
||||
var c2;
|
||||
|
||||
UIElements.getProperties = function (common, opts, cb) {
|
||||
var data;
|
||||
var content;
|
||||
var button = [{
|
||||
className: 'primary',
|
||||
name: Messages.okButton,
|
||||
className: 'cancel',
|
||||
name: Messages.filePicker_close,
|
||||
onClick: function () {},
|
||||
keys: [13]
|
||||
keys: [13,27]
|
||||
}];
|
||||
NThen(function (waitFor) {
|
||||
getPadProperties(common, data, waitFor(function (e, c) {
|
||||
c1 = UI.dialog.customModal(c[0], {
|
||||
buttons: button
|
||||
});
|
||||
getPropertiesData(common, opts, waitFor(function (e, _data) {
|
||||
if (e) {
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
data = _data;
|
||||
}));
|
||||
getRightsProperties(common, data, waitFor(function (e, c) {
|
||||
c2 = UI.dialog.customModal(c[0], {
|
||||
}).nThen(function (waitFor) {
|
||||
getPadProperties(common, data, opts, waitFor(function (e, c) {
|
||||
if (e) {
|
||||
waitFor.abort();
|
||||
return void cb(e);
|
||||
}
|
||||
content = UI.dialog.customModal(c[0], {
|
||||
buttons: button
|
||||
});
|
||||
}));
|
||||
}).nThen(function () {
|
||||
var tabs = UI.dialog.tabs([{
|
||||
title: Messages.fc_prop,
|
||||
content: c1
|
||||
}, {
|
||||
title: Messages.creation_propertiesTitle,
|
||||
content: c2
|
||||
icon: "fa fa-info-circle",
|
||||
content: content
|
||||
}]);
|
||||
cb (void 0, $(tabs));
|
||||
var modal = UI.openCustomModal(tabs);
|
||||
cb (void 0, modal);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -789,27 +950,37 @@ define([
|
||||
var name = data.displayName || data.name || Messages.anonymous;
|
||||
var avatar = h('span.cp-usergrid-avatar.cp-avatar');
|
||||
UIElements.displayAvatar(common, $(avatar), data.avatar, name);
|
||||
return h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
|
||||
var removeBtn, el;
|
||||
if (config.remove) {
|
||||
removeBtn = h('span.fa.fa-times');
|
||||
$(removeBtn).click(function () {
|
||||
config.remove(el);
|
||||
});
|
||||
}
|
||||
|
||||
el = h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
|
||||
'data-ed': data.edPublic,
|
||||
'data-teamid': data.teamId,
|
||||
'data-curve': data.curvePublic || '',
|
||||
'data-name': name.toLowerCase(),
|
||||
'data-order': i,
|
||||
title: name,
|
||||
style: 'order:'+i+';'
|
||||
},[
|
||||
avatar,
|
||||
h('span.cp-usergrid-user-name', name)
|
||||
h('span.cp-usergrid-user-name', name),
|
||||
data.notRemovable ? undefined : removeBtn
|
||||
]);
|
||||
return el;
|
||||
}).filter(function (x) { return x; });
|
||||
|
||||
var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : '';
|
||||
var classes = noOthers + (config.large?'.large':'') + (config.list?'.list':'');
|
||||
|
||||
var inputFilter = h('input', {
|
||||
placeholder: Messages.share_filterFriend
|
||||
});
|
||||
|
||||
var div = h('div.cp-usergrid-container' + noOthers + (config.large?'.large':''), [
|
||||
var div = h('div.cp-usergrid-container' + classes, [
|
||||
label ? h('label', label) : undefined,
|
||||
h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [
|
||||
inputFilter
|
||||
@@ -1140,8 +1311,13 @@ define([
|
||||
|
||||
var parsed = Hash.parsePadUrl(pathname);
|
||||
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
|
||||
var canBAR = parsed.type !== 'drive';
|
||||
|
||||
var burnAfterReading;
|
||||
var burnAfterReading = (hashes.viewHash && canBAR) ?
|
||||
UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, {
|
||||
mark: {tabindex:1},
|
||||
label: {style: "display: none;"}
|
||||
}) : undefined;
|
||||
var rights = h('div.msg.cp-inline-radio-group', [
|
||||
h('label', Messages.share_linkAccess),
|
||||
h('div.radio-group',[
|
||||
@@ -1151,8 +1327,7 @@ define([
|
||||
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
|
||||
UI.createRadio('accessRights', 'cp-share-editable-true',
|
||||
Messages.share_linkEdit, false, { mark: {tabindex:1} })]),
|
||||
burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading,
|
||||
false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined
|
||||
burnAfterReading
|
||||
]);
|
||||
|
||||
// Burn after reading
|
||||
@@ -1174,7 +1349,9 @@ define([
|
||||
return;
|
||||
}
|
||||
// When the burn after reading option is selected, transform the modal buttons
|
||||
$(burnAfterReading).show();
|
||||
$(burnAfterReading).css({
|
||||
display: 'flex'
|
||||
});
|
||||
});
|
||||
|
||||
var $rights = $(rights);
|
||||
@@ -2037,6 +2214,17 @@ define([
|
||||
if (data.accept) { $input.attr('accept', data.accept); }
|
||||
button.click(function () { $input.click(); });
|
||||
break;
|
||||
case 'copy':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-clone cp-toolbar-icon-import',
|
||||
title: Messages.makeACopy,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.makeACopy));
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
sframeChan.query('EV_MAKE_A_COPY');
|
||||
});
|
||||
break;
|
||||
case 'importtemplate':
|
||||
if (!AppConfig.enableTemplates) { return; }
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
@@ -2245,6 +2433,26 @@ define([
|
||||
});
|
||||
updateIcon(data.element.is(':visible'));
|
||||
break;
|
||||
case 'access':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-unlock-alt cp-toolbar-icon-access',
|
||||
title: Messages.accessButton,
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'})
|
||||
.text(Messages.accessButton))
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
common.isPadStored(function (err, data) {
|
||||
if (!data) {
|
||||
return void UI.alert(Messages.autostore_notAvailable);
|
||||
}
|
||||
require(['/common/inner/access.js'], function (Access) {
|
||||
Access.getAccessModal(common, {}, function (e) {
|
||||
if (e) { console.error(e); }
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'properties':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-info-circle cp-toolbar-icon-properties',
|
||||
@@ -2257,12 +2465,8 @@ define([
|
||||
if (!data) {
|
||||
return void UI.alert(Messages.autostore_notAvailable);
|
||||
}
|
||||
getPropertiesData(common, function (e, data) {
|
||||
UIElements.getProperties(common, {}, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
UIElements.getProperties(common, data, function (e, $prop) {
|
||||
if (e) { return void console.error(e); }
|
||||
UI.openCustomModal($prop[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2619,7 +2823,7 @@ define([
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
var getFirstEmojiOrCharacter = function (str) {
|
||||
var getFirstEmojiOrCharacter = UIElements.getFirstCharacter = function (str) {
|
||||
if (!str || !str.trim()) { return '?'; }
|
||||
var emojis = emojiStringToArray(str);
|
||||
return isEmoji(emojis[0])? emojis[0]: str[0];
|
||||
@@ -2633,13 +2837,13 @@ define([
|
||||
};
|
||||
UIElements.displayAvatar = function (common, $container, href, name, cb) {
|
||||
var displayDefault = function () {
|
||||
var text = getFirstEmojiOrCharacter(name);
|
||||
var text = (href && typeof(href) === "string") ? href : getFirstEmojiOrCharacter(name);
|
||||
var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
|
||||
$container.append($avatar);
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
|
||||
if (!href) { return void displayDefault(); }
|
||||
if (!href || href.length === 1) { return void displayDefault(); }
|
||||
|
||||
var centerImage = function ($img, $image, img) {
|
||||
var w = img.width;
|
||||
@@ -3191,7 +3395,14 @@ define([
|
||||
}
|
||||
options.push({ tag: 'hr' });
|
||||
// Add login or logout button depending on the current status
|
||||
if (accountName) {
|
||||
if (priv.loggedIn) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'class': 'cp-toolbar-menu-logout-everywhere fa fa-plug',
|
||||
},
|
||||
content: h('span', Messages.logoutEverywhere)
|
||||
});
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {'class': 'cp-toolbar-menu-logout fa fa-sign-out'},
|
||||
@@ -3278,6 +3489,12 @@ define([
|
||||
window.parent.location = origin+'/';
|
||||
});
|
||||
});
|
||||
|
||||
$userAdmin.find('a.cp-toolbar-menu-logout-everywhere').click(function () {
|
||||
Common.getSframeChannel().query('Q_LOGOUT_EVERYWHERE', null, function () {
|
||||
window.parent.location = origin + '/';
|
||||
});
|
||||
});
|
||||
$userAdmin.find('a.cp-toolbar-menu-settings').click(function () {
|
||||
if (padType) {
|
||||
window.open(origin+'/settings/');
|
||||
@@ -3731,7 +3948,7 @@ define([
|
||||
]);
|
||||
|
||||
var settings = h('div.cp-creation-remember', [
|
||||
UI.createCheckbox('cp-creation-remember', Messages.creation_saveSettings, false),
|
||||
UI.createCheckbox('cp-creation-remember', Messages.dontShowAgain, false),
|
||||
createHelper('/settings/#creation', Messages.creation_settings),
|
||||
h('div.cp-creation-remember-help.cp-creation-slider', [
|
||||
h('span.fa.fa-exclamation-circle.cp-creation-warning'),
|
||||
@@ -4015,7 +4232,7 @@ define([
|
||||
};
|
||||
|
||||
UIElements.onServerError = function (common, err, toolbar, cb) {
|
||||
if (["EDELETED", "EEXPIRED"].indexOf(err.type) === -1) { return; }
|
||||
//if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var msg = err.type;
|
||||
if (err.type === 'EEXPIRED') {
|
||||
@@ -4023,17 +4240,21 @@ define([
|
||||
if (err.loaded) {
|
||||
msg += Messages.errorCopy;
|
||||
}
|
||||
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
|
||||
} else if (err.type === 'EDELETED') {
|
||||
if (priv.burnAfterReading) { return void cb(); }
|
||||
msg = Messages.deletedError;
|
||||
if (err.loaded) {
|
||||
msg += Messages.errorCopy;
|
||||
}
|
||||
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
|
||||
} else if (err.type === 'ERESTRICTED') {
|
||||
msg = Messages.restrictedError;
|
||||
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
|
||||
}
|
||||
var sframeChan = common.getSframeChannel();
|
||||
sframeChan.event('EV_SHARE_OPEN', {hidden: true});
|
||||
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
|
||||
UI.errorLoadingScreen(msg, true, true);
|
||||
UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded));
|
||||
(cb || function () {})();
|
||||
};
|
||||
|
||||
@@ -4097,52 +4318,68 @@ define([
|
||||
};
|
||||
|
||||
var crowdfundingState = false;
|
||||
UIElements.displayCrowdfunding = function (common) {
|
||||
UIElements.displayCrowdfunding = function (common, force) {
|
||||
if (crowdfundingState) { return; }
|
||||
if (AppConfig.disableCrowdfundingMessages) { return; }
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
|
||||
|
||||
var todo = function () {
|
||||
crowdfundingState = true;
|
||||
// Display the popup
|
||||
var text = Messages.crowdfunding_popup_text;
|
||||
var yes = h('button.cp-corner-primary', [
|
||||
h('span.fa.fa-external-link'),
|
||||
'OpenCollective'
|
||||
]);
|
||||
var no = h('button.cp-corner-cancel', Messages.crowdfunding_popup_no);
|
||||
var actions = h('div', [no, yes]);
|
||||
|
||||
var dontShowAgain = function () {
|
||||
common.setAttribute(['general', 'crowdfunding'], false);
|
||||
Feedback.send('CROWDFUNDING_NEVER');
|
||||
};
|
||||
|
||||
var modal = UI.cornerPopup(text, actions, '', {
|
||||
big: true,
|
||||
alt: true,
|
||||
dontShowAgain: dontShowAgain
|
||||
});
|
||||
|
||||
$(yes).click(function () {
|
||||
modal.delete();
|
||||
common.openURL(priv.accounts.donateURL);
|
||||
Feedback.send('CROWDFUNDING_YES');
|
||||
});
|
||||
$(modal.popup).find('a').click(function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
modal.delete();
|
||||
common.openURL(priv.accounts.donateURL);
|
||||
Feedback.send('CROWDFUNDING_LINK');
|
||||
});
|
||||
$(no).click(function () {
|
||||
modal.delete();
|
||||
Feedback.send('CROWDFUNDING_NO');
|
||||
});
|
||||
};
|
||||
|
||||
if (force) {
|
||||
crowdfundingState = true;
|
||||
return void todo();
|
||||
}
|
||||
|
||||
if (AppConfig.disableCrowdfundingMessages) { return; }
|
||||
if (priv.plan) { return; }
|
||||
|
||||
crowdfundingState = true;
|
||||
setTimeout(function () {
|
||||
common.getAttribute(['general', 'crowdfunding'], function (err, val) {
|
||||
if (err || val === false) { return; }
|
||||
common.getSframeChannel().query('Q_GET_PINNED_USAGE', null, function (err, obj) {
|
||||
var quotaMb = obj.quota / (1024 * 1024);
|
||||
if (quotaMb < 10) { return; }
|
||||
// Display the popup
|
||||
var text = Messages.crowdfunding_popup_text;
|
||||
var yes = h('button.cp-corner-primary', Messages.crowdfunding_popup_yes);
|
||||
var no = h('button.cp-corner-primary', Messages.crowdfunding_popup_no);
|
||||
var never = h('button.cp-corner-cancel', Messages.crowdfunding_popup_never);
|
||||
var actions = h('div', [yes, no, never]);
|
||||
|
||||
var modal = UI.cornerPopup(text, actions, null, {big: true});
|
||||
|
||||
$(yes).click(function () {
|
||||
modal.delete();
|
||||
common.openURL(priv.accounts.donateURL);
|
||||
Feedback.send('CROWDFUNDING_YES');
|
||||
});
|
||||
$(modal.popup).find('a').click(function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
modal.delete();
|
||||
common.openURL(priv.accounts.donateURL);
|
||||
Feedback.send('CROWDFUNDING_LINK');
|
||||
});
|
||||
$(no).click(function () {
|
||||
modal.delete();
|
||||
Feedback.send('CROWDFUNDING_NO');
|
||||
});
|
||||
$(never).click(function () {
|
||||
modal.delete();
|
||||
common.setAttribute(['general', 'crowdfunding'], false);
|
||||
Feedback.send('CROWDFUNDING_NEVER');
|
||||
});
|
||||
});
|
||||
common.getAttribute(['general', 'crowdfunding'], function (err, val) {
|
||||
if (err || val === false) { return; }
|
||||
common.getSframeChannel().query('Q_GET_PINNED_USAGE', null, function (err, obj) {
|
||||
var quotaMb = obj.quota / (1024 * 1024);
|
||||
if (quotaMb < 10) { return; }
|
||||
todo();
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
|
||||
var storePopupState = false;
|
||||
@@ -4164,11 +4401,14 @@ define([
|
||||
|
||||
var hide = h('button.cp-corner-cancel', Messages.autostore_hide);
|
||||
var store = h('button.cp-corner-primary', Messages.autostore_store);
|
||||
var actions = h('div', [store, hide]);
|
||||
var actions = h('div', [hide, store]);
|
||||
|
||||
var initialHide = data && data.autoStore && data.autoStore === -1;
|
||||
var modal = UI.cornerPopup(text, actions, footer, {hidden: initialHide});
|
||||
|
||||
// Once the store pad popup is created, put the crowdfunding one in the queue
|
||||
UIElements.displayCrowdfunding(common);
|
||||
|
||||
autoStoreModal[priv.channel] = modal;
|
||||
|
||||
$(modal.popup).find('.cp-corner-footer a').click(function (e) {
|
||||
@@ -4177,7 +4417,6 @@ define([
|
||||
});
|
||||
|
||||
$(hide).click(function () {
|
||||
UIElements.displayCrowdfunding(common);
|
||||
delete autoStoreModal[priv.channel];
|
||||
modal.delete();
|
||||
});
|
||||
@@ -4197,7 +4436,6 @@ define([
|
||||
$(document).trigger('cpPadStored');
|
||||
delete autoStoreModal[priv.channel];
|
||||
modal.delete();
|
||||
UIElements.displayCrowdfunding(common);
|
||||
UI.log(Messages.autostore_saved);
|
||||
});
|
||||
});
|
||||
@@ -4319,7 +4557,8 @@ define([
|
||||
|
||||
UIElements.displayFriendRequestModal = function (common, data) {
|
||||
var msg = data.content.msg;
|
||||
var text = Messages._getKey('contacts_request', [Util.fixHTML(msg.content.displayName)]);
|
||||
var userData = msg.content.user;
|
||||
var text = Messages._getKey('contacts_request', [Util.fixHTML(userData.displayName)]);
|
||||
|
||||
var todo = function (yes) {
|
||||
common.getSframeChannel().query("Q_ANSWER_FRIEND_REQUEST", {
|
||||
@@ -4346,7 +4585,6 @@ define([
|
||||
|
||||
UIElements.displayAddOwnerModal = function (common, data) {
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var user = common.getMetadataMgr().getUserData();
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var msg = data.content.msg;
|
||||
|
||||
@@ -4381,15 +4619,7 @@ define([
|
||||
href: msg.content.href,
|
||||
password: msg.content.password,
|
||||
title: msg.content.title,
|
||||
answer: yes,
|
||||
user: {
|
||||
displayName: user.name,
|
||||
avatar: user.avatar,
|
||||
profile: user.profile,
|
||||
notifications: user.notifications,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: priv.edPublic
|
||||
}
|
||||
answer: yes
|
||||
}, {
|
||||
channel: msg.content.user.notifications,
|
||||
curvePublic: msg.content.user.curvePublic
|
||||
@@ -4470,7 +4700,6 @@ define([
|
||||
};
|
||||
UIElements.displayAddTeamOwnerModal = function (common, data) {
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var user = common.getMetadataMgr().getUserData();
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var msg = data.content.msg;
|
||||
|
||||
@@ -4487,15 +4716,7 @@ define([
|
||||
common.mailbox.sendTo("ADD_OWNER_ANSWER", {
|
||||
teamChannel: msg.content.teamChannel,
|
||||
title: msg.content.title,
|
||||
answer: yes,
|
||||
user: {
|
||||
displayName: user.name,
|
||||
avatar: user.avatar,
|
||||
profile: user.profile,
|
||||
notifications: user.notifications,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: priv.edPublic
|
||||
}
|
||||
answer: yes
|
||||
}, {
|
||||
channel: msg.content.user.notifications,
|
||||
curvePublic: msg.content.user.curvePublic
|
||||
@@ -4602,17 +4823,15 @@ define([
|
||||
var f = priv.friends[curve];
|
||||
$verified.append(h('span.fa.fa-certificate'));
|
||||
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);
|
||||
} else {
|
||||
$verified.append(Messages._getKey('requestEdit_fromStranger', [name]));
|
||||
$verified.append(Messages._getKey('isNotContact', [name]));
|
||||
}
|
||||
return verified;
|
||||
};
|
||||
|
||||
UIElements.displayInviteTeamModal = function (common, data) {
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var user = common.getMetadataMgr().getUserData();
|
||||
var msg = data.content.msg;
|
||||
|
||||
var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
|
||||
@@ -4633,15 +4852,7 @@ define([
|
||||
common.mailbox.sendTo("INVITE_TO_TEAM_ANSWER", {
|
||||
answer: yes,
|
||||
teamChannel: msg.content.team.channel,
|
||||
teamName: teamName,
|
||||
user: {
|
||||
displayName: user.name,
|
||||
avatar: user.avatar,
|
||||
profile: user.profile,
|
||||
notifications: user.notifications,
|
||||
curvePublic: user.curvePublic,
|
||||
edPublic: priv.edPublic
|
||||
}
|
||||
teamName: teamName
|
||||
}, {
|
||||
channel: msg.content.user.notifications,
|
||||
curvePublic: msg.content.user.curvePublic
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
};
|
||||
|
||||
Util.mkAsync = function (f) {
|
||||
if (typeof(f) !== 'function') {
|
||||
throw new Error('EXPECTED_FUNCTION');
|
||||
}
|
||||
return function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
setTimeout(function () {
|
||||
@@ -65,6 +68,19 @@
|
||||
};
|
||||
};
|
||||
|
||||
Util.mkTimeout = function (_f, ms) {
|
||||
ms = ms || 0;
|
||||
var f = Util.once(_f);
|
||||
|
||||
var timeout = setTimeout(function () {
|
||||
f('TIMEOUT');
|
||||
}, ms);
|
||||
|
||||
return Util.both(f, function () {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
};
|
||||
|
||||
Util.response = function () {
|
||||
var pending = {};
|
||||
var timeouts = {};
|
||||
@@ -224,6 +240,7 @@
|
||||
else if (bytes >= oneMegabyte) { return 'MB'; }
|
||||
};
|
||||
|
||||
|
||||
// given a path, asynchronously return an arraybuffer
|
||||
Util.fetch = function (src, cb, progress) {
|
||||
var CB = Util.once(cb);
|
||||
@@ -268,8 +285,8 @@
|
||||
Util.throttle = function (f, ms) {
|
||||
var to;
|
||||
var g = function () {
|
||||
window.clearTimeout(to);
|
||||
to = window.setTimeout(Util.bake(f, Util.slice(arguments)), ms);
|
||||
clearTimeout(to);
|
||||
to = setTimeout(Util.bake(f, Util.slice(arguments)), ms);
|
||||
};
|
||||
return g;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ define([
|
||||
'/common/common-messaging.js',
|
||||
'/common/common-constants.js',
|
||||
'/common/common-feedback.js',
|
||||
'/common/visible.js',
|
||||
'/common/userObject.js',
|
||||
'/common/outer/local-store.js',
|
||||
'/common/outer/worker-channel.js',
|
||||
@@ -14,7 +15,7 @@ define([
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function (Config, Messages, Util, Hash,
|
||||
Messaging, Constants, Feedback, UserObject, LocalStore, Channel, Block,
|
||||
Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
|
||||
AppConfig, Nthen) {
|
||||
|
||||
/* This file exposes functionality which is specific to Cryptpad, but not to
|
||||
@@ -49,6 +50,12 @@ define([
|
||||
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.getLanguage = function () {
|
||||
return Messages._languageUsed;
|
||||
@@ -374,7 +381,7 @@ define([
|
||||
|
||||
|
||||
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) {
|
||||
if (obj && obj.error) { return void cb(obj.error); }
|
||||
cb(null, obj);
|
||||
@@ -394,7 +401,7 @@ define([
|
||||
|
||||
common.setPadAttribute = function (attr, value, cb, href) {
|
||||
cb = cb || function () {};
|
||||
href = Hash.getRelativeHref(href || window.location.href);
|
||||
href = Hash.getRelativeHref(href || currentPad.href);
|
||||
postMessage("SET_PAD_ATTRIBUTE", {
|
||||
href: href,
|
||||
attr: attr,
|
||||
@@ -405,7 +412,7 @@ define([
|
||||
});
|
||||
};
|
||||
common.getPadAttribute = function (attr, cb, href) {
|
||||
href = Hash.getRelativeHref(href || window.location.href);
|
||||
href = Hash.getRelativeHref(href || currentPad.href);
|
||||
if (!href) {
|
||||
return void cb('E404');
|
||||
}
|
||||
@@ -505,7 +512,7 @@ define([
|
||||
};
|
||||
|
||||
common.saveAsTemplate = function (Cryptput, data, cb) {
|
||||
var p = Hash.parsePadUrl(window.location.href);
|
||||
var p = Hash.parsePadUrl(currentPad.href);
|
||||
if (!p.type) { return; }
|
||||
// PPP: password for the new template?
|
||||
var hash = Hash.createRandomHash(p.type);
|
||||
@@ -537,13 +544,35 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var fixPadMetadata = function (parsed, copy) {
|
||||
var meta;
|
||||
if (Array.isArray(parsed) && typeof(parsed[3]) === "object") {
|
||||
meta = parsed[3].metadata; // pad
|
||||
} else if (parsed.info) {
|
||||
meta = parsed.info; // poll
|
||||
} else {
|
||||
meta = parsed.metadata;
|
||||
}
|
||||
if (typeof(meta) === "object") {
|
||||
meta.defaultTitle = meta.title || meta.defaultTitle;
|
||||
if (copy) {
|
||||
meta.defaultTitle = Messages._getKey('copy_title', [meta.defaultTitle]);
|
||||
}
|
||||
meta.title = "";
|
||||
delete meta.users;
|
||||
delete meta.chat2;
|
||||
delete meta.chat;
|
||||
delete meta.cursor;
|
||||
}
|
||||
};
|
||||
|
||||
common.useTemplate = function (data, Crypt, cb, optsPut) {
|
||||
// opts is used to overrides options for chainpad-netflux in cryptput
|
||||
// it allows us to add owners and expiration time if it is a new file
|
||||
var href = data.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"); }
|
||||
postMessage("INCREMENT_TEMPLATE_USE", href);
|
||||
|
||||
@@ -570,24 +599,7 @@ define([
|
||||
try {
|
||||
// Try to fix the title before importing the template
|
||||
var parsed = JSON.parse(val);
|
||||
var meta;
|
||||
if (Array.isArray(parsed) && typeof(parsed[3]) === "object") {
|
||||
meta = parsed[3].metadata; // pad
|
||||
} else if (parsed.info) {
|
||||
meta = parsed.info; // poll
|
||||
} else {
|
||||
meta = parsed.metadata;
|
||||
}
|
||||
if (typeof(meta) === "object") {
|
||||
meta.defaultTitle = meta.title || meta.defaultTitle;
|
||||
meta.title = "";
|
||||
delete meta.users;
|
||||
delete meta.chat2;
|
||||
delete meta.chat;
|
||||
delete meta.cursor;
|
||||
if (data.chat) { meta.chat2 = data.chat; }
|
||||
if (data.cursor) { meta.cursor = data.cursor; }
|
||||
}
|
||||
fixPadMetadata(parsed);
|
||||
val = JSON.stringify(parsed);
|
||||
} catch (e) {
|
||||
console.log("Can't fix template title", e);
|
||||
@@ -601,66 +613,104 @@ define([
|
||||
var fileHost = Config.fileHost || window.location.origin;
|
||||
var data = common.fromFileData;
|
||||
var parsed = Hash.parsePadUrl(data.href);
|
||||
var parsed2 = Hash.parsePadUrl(window.location.href);
|
||||
var hash = parsed.hash;
|
||||
var name = data.title;
|
||||
var secret = Hash.getSecrets('file', hash, data.password);
|
||||
var src = fileHost + Hash.getBlobPathFromHex(secret.channel);
|
||||
var key = secret.keys && secret.keys.cryptKey;
|
||||
var parsed2 = Hash.parsePadUrl(currentPad.href);
|
||||
|
||||
if (parsed2.type === 'poll') { optsPut.initialState = '{}'; }
|
||||
|
||||
var u8;
|
||||
var res;
|
||||
var mode;
|
||||
var val;
|
||||
Nthen(function(waitFor) {
|
||||
Util.fetch(src, waitFor(function (err, _u8) {
|
||||
if (err) { return void waitFor.abort(); }
|
||||
u8 = _u8;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
require(["/file/file-crypto.js"], waitFor(function (FileCrypto) {
|
||||
FileCrypto.decrypt(u8, key, waitFor(function (err, _res) {
|
||||
if (err || !_res.content) { return void waitFor.abort(); }
|
||||
res = _res;
|
||||
}));
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
var ext = Util.parseFilename(data.title).ext;
|
||||
if (!ext) {
|
||||
mode = "text";
|
||||
Nthen(function(_waitFor) {
|
||||
// If pad, use cryptget
|
||||
if (parsed.hashData && parsed.hashData.type === 'pad') {
|
||||
var optsGet = {
|
||||
password: data.password,
|
||||
initialState: parsed.type === 'poll' ? '{}' : undefined
|
||||
};
|
||||
Crypt.get(parsed.hash, _waitFor(function (err, _val) {
|
||||
if (err) {
|
||||
_waitFor.abort();
|
||||
return void cb();
|
||||
}
|
||||
try {
|
||||
val = JSON.parse(_val);
|
||||
fixPadMetadata(val, true);
|
||||
} catch (e) {
|
||||
_waitFor.abort();
|
||||
return void cb();
|
||||
}
|
||||
}), optsGet);
|
||||
return;
|
||||
}
|
||||
require(["/common/modes.js"], waitFor(function (Modes) {
|
||||
Modes.list.some(function (fType) {
|
||||
if (fType.ext === ext) {
|
||||
mode = fType.mode;
|
||||
return true;
|
||||
|
||||
var name = data.title;
|
||||
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
|
||||
var src = fileHost + Hash.getBlobPathFromHex(secret.channel);
|
||||
var key = secret.keys && secret.keys.cryptKey;
|
||||
var u8;
|
||||
var res;
|
||||
var mode;
|
||||
|
||||
// Otherwise, it's a text blob "open in code": get blob data & convert format
|
||||
Nthen(function (waitFor) {
|
||||
Util.fetch(src, waitFor(function (err, _u8) {
|
||||
if (err) {
|
||||
_waitFor.abort();
|
||||
return void waitFor.abort();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
var reader = new FileReader();
|
||||
reader.addEventListener('loadend', waitFor(function (e) {
|
||||
val = {
|
||||
content: e.srcElement.result,
|
||||
highlightMode: mode,
|
||||
metadata: {
|
||||
defaultTitle: name,
|
||||
title: name,
|
||||
type: "code",
|
||||
},
|
||||
};
|
||||
}));
|
||||
reader.readAsText(res.content);
|
||||
u8 = _u8;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
require(["/file/file-crypto.js"], waitFor(function (FileCrypto) {
|
||||
FileCrypto.decrypt(u8, key, waitFor(function (err, _res) {
|
||||
if (err || !_res.content) {
|
||||
_waitFor.abort();
|
||||
return void waitFor.abort();
|
||||
}
|
||||
res = _res;
|
||||
}));
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
var ext = Util.parseFilename(data.title).ext;
|
||||
if (!ext) {
|
||||
mode = "text";
|
||||
return;
|
||||
}
|
||||
require(["/common/modes.js"], waitFor(function (Modes) {
|
||||
Modes.list.some(function (fType) {
|
||||
if (fType.ext === ext) {
|
||||
mode = fType.mode;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
var reader = new FileReader();
|
||||
reader.addEventListener('loadend', waitFor(function (e) {
|
||||
val = {
|
||||
content: e.srcElement.result,
|
||||
highlightMode: mode,
|
||||
metadata: {
|
||||
defaultTitle: name,
|
||||
title: name,
|
||||
type: "code",
|
||||
},
|
||||
};
|
||||
}));
|
||||
reader.readAsText(res.content);
|
||||
}).nThen(_waitFor());
|
||||
}).nThen(function () {
|
||||
Crypt.put(parsed2.hash, JSON.stringify(val), cb, optsPut);
|
||||
Crypt.put(parsed2.hash, JSON.stringify(val), function () {
|
||||
cb();
|
||||
Crypt.get(parsed2.hash, function (err, val) {
|
||||
console.warn(val);
|
||||
});
|
||||
}, optsPut);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Forget button
|
||||
common.moveToTrash = function (cb, href) {
|
||||
href = href || window.location.href;
|
||||
href = href || currentPad.href;
|
||||
postMessage("MOVE_TO_TRASH", { href: href }, cb);
|
||||
};
|
||||
|
||||
@@ -668,7 +718,7 @@ define([
|
||||
common.setPadTitle = function (data, cb) {
|
||||
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);
|
||||
if (!parsed.hash) { return cb ('Invalid hash'); }
|
||||
data.href = parsed.getUrl({present: parsed.present});
|
||||
@@ -698,7 +748,7 @@ define([
|
||||
if (obj.error !== "EAUTH") { console.log("unable to set pad title"); }
|
||||
return void cb(obj.error);
|
||||
}
|
||||
cb();
|
||||
cb(null, obj);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -755,6 +805,13 @@ define([
|
||||
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
|
||||
@@ -832,6 +889,7 @@ define([
|
||||
pad.onConnectEvent = Util.mkEvent();
|
||||
pad.onErrorEvent = Util.mkEvent();
|
||||
pad.onMetadataEvent = Util.mkEvent();
|
||||
pad.onChannelDeleted = Util.mkEvent();
|
||||
|
||||
pad.requestAccess = function (data, cb) {
|
||||
postMessage("REQUEST_PAD_ACCESS", data, cb);
|
||||
@@ -1023,11 +1081,12 @@ define([
|
||||
}, waitFor());
|
||||
}
|
||||
}).nThen(function () {
|
||||
common.drive.onChange.fire({path: ['drive', Constants.storageKey]});
|
||||
cb({
|
||||
warning: warning,
|
||||
hash: newHash,
|
||||
href: newHref,
|
||||
roHref: newRoHref
|
||||
roHref: newRoHref,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1156,6 +1215,7 @@ define([
|
||||
channel: newSecret.channel
|
||||
}, waitFor());
|
||||
}).nThen(function () {
|
||||
common.drive.onChange.fire({path: ['drive', Constants.storageKey]});
|
||||
cb({
|
||||
warning: warning,
|
||||
hash: newHash,
|
||||
@@ -1390,6 +1450,7 @@ define([
|
||||
}, waitFor());
|
||||
}));
|
||||
}).nThen(function () {
|
||||
common.drive.onChange.fire({path: ['drive', Constants.storageKey]});
|
||||
cb({
|
||||
warning: warning,
|
||||
hash: newHash,
|
||||
@@ -1608,7 +1669,7 @@ define([
|
||||
hashes = Hash.getHashes(secret);
|
||||
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'); }
|
||||
hashes = Hash.getHashes(secret);
|
||||
|
||||
@@ -1651,9 +1712,9 @@ define([
|
||||
var stored = currentVersion || '0.0.0';
|
||||
var storedArr = stored.split('.');
|
||||
storedArr[2] = 0;
|
||||
var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) ||
|
||||
var shouldUpdate = parseInt(verArr[0]) !== parseInt(storedArr[0]) ||
|
||||
(parseInt(verArr[0]) === parseInt(storedArr[0]) &&
|
||||
parseInt(verArr[1]) > parseInt(storedArr[1]));
|
||||
parseInt(verArr[1]) !== parseInt(storedArr[1]));
|
||||
if (!shouldUpdate) { return; }
|
||||
currentVersion = ver;
|
||||
localStorage[CRYPTPAD_VERSION] = ver;
|
||||
@@ -1679,7 +1740,7 @@ define([
|
||||
LocalStore.logout();
|
||||
|
||||
// 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/';
|
||||
};
|
||||
|
||||
@@ -1689,19 +1750,37 @@ define([
|
||||
cb();
|
||||
};
|
||||
|
||||
var lastPing = +new Date();
|
||||
var onPing = function (data, cb) {
|
||||
lastPing = +new Date();
|
||||
cb();
|
||||
};
|
||||
|
||||
var timeout = false;
|
||||
common.onTimeoutEvent = Util.mkEvent();
|
||||
var onTimeout = function () {
|
||||
var onTimeout = function (fromOuter) {
|
||||
var key = fromOuter ? "TIMEOUT_OUTER" : "TIMEOUT_KICK";
|
||||
Feedback.send(key, true);
|
||||
timeout = true;
|
||||
common.onNetworkDisconnect.fire();
|
||||
common.padRpc.onDisconnectEvent.fire();
|
||||
common.onTimeoutEvent.fire();
|
||||
};
|
||||
|
||||
Visible.onChange(function (visible) {
|
||||
if (!visible) { return; }
|
||||
var now = +new Date();
|
||||
// If last ping is bigger than 2min, ping the worker
|
||||
if (now - lastPing > (2 * 60 * 1000)) {
|
||||
var to = setTimeout(function () {
|
||||
onTimeout(true);
|
||||
}, 5000);
|
||||
postMessage('PING', null, function () {
|
||||
clearTimeout(to);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var queries = {
|
||||
PING: onPing,
|
||||
TIMEOUT: onTimeout,
|
||||
@@ -1740,6 +1819,7 @@ define([
|
||||
PAD_CONNECT: common.padRpc.onConnectEvent.fire,
|
||||
PAD_ERROR: common.padRpc.onErrorEvent.fire,
|
||||
PAD_METADATA: common.padRpc.onMetadataEvent.fire,
|
||||
CHANNEL_DELETED: common.padRpc.onChannelDeleted.fire,
|
||||
// Drive
|
||||
DRIVE_LOG: common.drive.onLog.fire,
|
||||
DRIVE_CHANGE: common.drive.onChange.fire,
|
||||
@@ -1780,6 +1860,11 @@ define([
|
||||
|
||||
return function (f, rdyCfg) {
|
||||
rdyCfg = rdyCfg || {};
|
||||
|
||||
if (rdyCfg.currentPad) {
|
||||
currentPad = common.currentPad = rdyCfg.currentPad;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
return void setTimeout(function () { f(void 0, env); });
|
||||
}
|
||||
@@ -1878,11 +1963,14 @@ define([
|
||||
anonHash: LocalStore.getFSHash(),
|
||||
localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), // TODO move this to LocalStore ?
|
||||
language: common.getLanguage(),
|
||||
driveEvents: rdyCfg.driveEvents // Boolean
|
||||
driveEvents: true //rdyCfg.driveEvents // Boolean
|
||||
};
|
||||
// if a pad is created from a file
|
||||
if (sessionStorage[Constants.newPadFileData]) {
|
||||
common.fromFileData = JSON.parse(sessionStorage[Constants.newPadFileData]);
|
||||
var _parsed1 = Hash.parsePadUrl(common.fromFileData.href);
|
||||
var _parsed2 = Hash.parsePadUrl(window.location.href);
|
||||
if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; }
|
||||
delete sessionStorage[Constants.newPadFileData];
|
||||
}
|
||||
|
||||
@@ -2101,7 +2189,10 @@ define([
|
||||
var parsedNew = Hash.parsePadUrl(newHref);
|
||||
if (parsedOld.hashData && parsedNew.hashData &&
|
||||
parsedOld.getUrl() !== parsedNew.getUrl()) {
|
||||
if (!parsedOld.hashData.key) { oldHref = newHref; return; }
|
||||
if (parsedOld.hashData.version !== 3 && !parsedOld.hashData.key) {
|
||||
oldHref = newHref;
|
||||
return;
|
||||
}
|
||||
// If different, reload
|
||||
document.location.reload();
|
||||
return;
|
||||
|
||||
@@ -55,7 +55,6 @@ define([
|
||||
var a = h('a.cp-md-toc-link', {
|
||||
href: '#',
|
||||
'data-href': obj.id,
|
||||
title: obj.title
|
||||
});
|
||||
a.innerHTML = obj.title;
|
||||
content.push(h('p.cp-md-toc-'+level, ['• ', a]));
|
||||
@@ -310,15 +309,24 @@ define([
|
||||
var mermaid_source = [];
|
||||
var mermaid_cache = {};
|
||||
|
||||
var canonicalizeMermaidSource = function (src) {
|
||||
// ignore changes to empty lines, since that won't affect
|
||||
// since it will have no effect on the rendered charts
|
||||
return src.replace(/\n[ \t]*\n*[ \t]*\n/g, '\n');
|
||||
};
|
||||
|
||||
// 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;
|
||||
var src = canonicalizeMermaidSource(el.childNodes[0].wholeText);
|
||||
el.setAttribute('mermaid-source', src);
|
||||
mermaid_source[index] = src;
|
||||
}
|
||||
});
|
||||
|
||||
// remember the previous scroll position
|
||||
var $parent = $content.parent();
|
||||
var scrollTop = $parent.scrollTop();
|
||||
// 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
|
||||
@@ -389,7 +397,12 @@ define([
|
||||
var cached = mermaid_cache[src];
|
||||
|
||||
// check if you had cached a pre-rendered instance of the supplied source
|
||||
if (typeof(cached) !== 'object') { return; }
|
||||
if (typeof(cached) !== 'object') {
|
||||
try {
|
||||
Mermaid.init(undefined, $(el));
|
||||
} catch (e) { console.error(e); }
|
||||
return;
|
||||
}
|
||||
|
||||
// if there's a cached rendering, empty out the contained source code
|
||||
// which would otherwise be drawn again.
|
||||
@@ -400,12 +413,9 @@ define([
|
||||
// and set a flag indicating that this graph need not be reprocessed
|
||||
el.setAttribute('data-processed', true);
|
||||
});
|
||||
|
||||
try {
|
||||
// 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); }
|
||||
}
|
||||
// recover the previous scroll position to avoid jank
|
||||
$parent.scrollTop(scrollTop);
|
||||
};
|
||||
|
||||
return DiffMd;
|
||||
|
||||
@@ -8,6 +8,9 @@ define([
|
||||
'/common/common-interface.js',
|
||||
'/common/common-constants.js',
|
||||
'/common/common-feedback.js',
|
||||
|
||||
'/common/inner/access.js',
|
||||
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/proxy-manager.js',
|
||||
@@ -23,6 +26,7 @@ define([
|
||||
UI,
|
||||
Constants,
|
||||
Feedback,
|
||||
Access,
|
||||
nThen,
|
||||
h,
|
||||
ProxyManager,
|
||||
@@ -78,7 +82,9 @@ define([
|
||||
var faRename = 'fa-pencil';
|
||||
var faColor = 'cptools-palette';
|
||||
var faTrash = 'fa-trash';
|
||||
var faCopy = 'fa-clone';
|
||||
var faDelete = 'fa-eraser';
|
||||
var faAccess = 'fa-unlock-alt';
|
||||
var faProperties = 'fa-info-circle';
|
||||
var faTags = 'fa-hashtag';
|
||||
var faUploadFiles = 'cptools-file-upload';
|
||||
@@ -116,9 +122,9 @@ define([
|
||||
var $addIcon = $('<span>', {"class": "fa fa-plus"});
|
||||
var $renamedIcon = $('<span>', {"class": "fa fa-flag"});
|
||||
var $readonlyIcon = $('<span>', {"class": "fa " + faReadOnly});
|
||||
var $ownedIcon = $('<span>', {"class": "fa fa-id-card-o"});
|
||||
var $ownedIcon = $('<span>', {"class": "fa fa-id-badge"});
|
||||
var $sharedIcon = $('<span>', {"class": "fa " + faShared});
|
||||
var $ownerIcon = $('<span>', {"class": "fa fa-id-card"});
|
||||
//var $ownerIcon = $('<span>', {"class": "fa fa-id-card"});
|
||||
var $tagsIcon = $('<span>', {"class": "fa " + faTags});
|
||||
var $passwordIcon = $('<span>', {"class": "fa fa-lock"});
|
||||
var $expirableIcon = $('<span>', {"class": "fa fa-clock-o"});
|
||||
@@ -319,6 +325,10 @@ define([
|
||||
'tabindex': '-1',
|
||||
'data-icon': faOpenInCode,
|
||||
}, Messages.fc_openInCode)),
|
||||
h('li', h('a.cp-app-drive-context-savelocal.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': 'fa-cloud-upload',
|
||||
}, Messages.pad_mediatagImport)), // Save in your CryptDrive
|
||||
$separator.clone()[0],
|
||||
h('li', h('a.cp-app-drive-context-expandall.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
@@ -338,14 +348,10 @@ define([
|
||||
'tabindex': '-1',
|
||||
'data-icon': 'fa-shhare-alt',
|
||||
}, Messages.shareButton)),
|
||||
h('li', h('a.cp-app-drive-context-savelocal.dropdown-item', {
|
||||
h('li', h('a.cp-app-drive-context-access.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': 'fa-cloud-upload',
|
||||
}, Messages.pad_mediatagImport)), // Save in your CryptDrive
|
||||
h('li', h('a.cp-app-drive-context-download.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faDownload,
|
||||
}, Messages.download_mt_button)),
|
||||
'data-icon': faAccess,
|
||||
}, Messages.accessButton)),
|
||||
$separator.clone()[0],
|
||||
h('li', h('a.cp-app-drive-context-newfolder.dropdown-item.cp-app-drive-context-editable', {
|
||||
'tabindex': '-1',
|
||||
@@ -431,6 +437,14 @@ define([
|
||||
'data-icon': faTags,
|
||||
}, Messages.fc_hashtag)),
|
||||
$separator.clone()[0],
|
||||
h('li', h('a.cp-app-drive-context-makeacopy.dropdown-item.cp-app-drive-context-editable', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faCopy,
|
||||
}, Messages.makeACopy)),
|
||||
h('li', h('a.cp-app-drive-context-download.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faDownload,
|
||||
}, Messages.download_mt_button)),
|
||||
h('li', h('a.cp-app-drive-context-delete.dropdown-item.cp-app-drive-context-editable', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faTrash,
|
||||
@@ -451,7 +465,7 @@ define([
|
||||
h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
|
||||
'tabindex': '-1',
|
||||
'data-icon': faProperties,
|
||||
}, Messages.fc_prop)),
|
||||
}, Messages.fc_prop))
|
||||
])
|
||||
]);
|
||||
// add icons to the contextmenu options
|
||||
@@ -587,7 +601,7 @@ define([
|
||||
var displayedCategories = [ROOT, TRASH, SEARCH, RECENT];
|
||||
|
||||
// PCS enabled: display owned pads
|
||||
if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); }
|
||||
//if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); }
|
||||
// Templates enabled: display template category
|
||||
if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); }
|
||||
// Tags used: display Tags category
|
||||
@@ -1029,15 +1043,26 @@ define([
|
||||
return ret;
|
||||
};
|
||||
|
||||
var openFile = function (el, href) {
|
||||
if (!href) {
|
||||
var data = manager.getFileData(el);
|
||||
if (!data || (!data.href && !data.roHref)) {
|
||||
return void logError("Missing data for the file", el, data);
|
||||
}
|
||||
href = data.href || data.roHref;
|
||||
var openFile = function (el, isRo) {
|
||||
var data = manager.getFileData(el);
|
||||
if (!data || (!data.href && !data.roHref)) {
|
||||
return void logError("Missing data for the file", el, data);
|
||||
}
|
||||
window.open(APP.origin + href);
|
||||
var href = isRo ? data.roHref : (data.href || data.roHref);
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
|
||||
if (useUnsafe !== false) { // true of undefined: use unsafe links
|
||||
return void window.open(APP.origin + href);
|
||||
}
|
||||
|
||||
// Get hidden hash
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
|
||||
var opts = {};
|
||||
if (isRo) { opts.view = true; }
|
||||
var hash = Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
|
||||
var hiddenHref = Hash.hashToHref(hash, parsed.type);
|
||||
window.open(APP.origin + hiddenHref);
|
||||
};
|
||||
|
||||
var refresh = APP.refresh = function () {
|
||||
@@ -1164,13 +1189,12 @@ define([
|
||||
} else if ($element.is('.cp-app-drive-element-noreadonly')) {
|
||||
hide.push('openro'); // Remove open 'view' mode
|
||||
}
|
||||
// if it's not a plain text file
|
||||
// XXX: there is a bug with this code in anon shared folder, so we disable it
|
||||
if (APP.loggedIn || !APP.newSharedFolder) {
|
||||
var metadata = manager.getFileData(manager.find(path));
|
||||
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
|
||||
hide.push('openincode');
|
||||
}
|
||||
var metadata = manager.getFileData(manager.find(path));
|
||||
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
|
||||
hide.push('openincode');
|
||||
}
|
||||
if (!metadata.channel || metadata.channel.length > 32 || metadata.rtChannel) {
|
||||
hide.push('makeacopy'); // Not for blobs
|
||||
}
|
||||
} else if ($element.is('.cp-app-drive-element-sharedf')) {
|
||||
if (containsFolder) {
|
||||
@@ -1184,6 +1208,7 @@ define([
|
||||
hide.push('openincode');
|
||||
hide.push('hashtag');
|
||||
hide.push('delete');
|
||||
hide.push('makeacopy');
|
||||
//hide.push('deleteowned');
|
||||
} else { // it's a folder
|
||||
if (containsFolder) {
|
||||
@@ -1193,12 +1218,12 @@ define([
|
||||
hide.push('collapseall');
|
||||
}
|
||||
containsFolder = true;
|
||||
hide.push('share'); // XXX CONVERT
|
||||
hide.push('savelocal'); // XXX CONVERT
|
||||
hide.push('savelocal');
|
||||
hide.push('openro');
|
||||
hide.push('openincode');
|
||||
hide.push('properties');
|
||||
hide.push('properties', 'access');
|
||||
hide.push('hashtag');
|
||||
hide.push('makeacopy');
|
||||
}
|
||||
// If we're in the trash, hide restore and properties for non-root elements
|
||||
if (type === "trash" && path && path.length > 4) {
|
||||
@@ -1227,7 +1252,7 @@ define([
|
||||
});
|
||||
if (paths.length > 1) {
|
||||
hide.push('restore');
|
||||
hide.push('properties');
|
||||
hide.push('properties', 'access');
|
||||
hide.push('rename');
|
||||
hide.push('openparent');
|
||||
hide.push('hashtag');
|
||||
@@ -1235,6 +1260,7 @@ define([
|
||||
hide.push('share');
|
||||
hide.push('savelocal');
|
||||
hide.push('openincode'); // can't because of race condition
|
||||
hide.push('makeacopy');
|
||||
}
|
||||
if (containsFolder && paths.length > 1) {
|
||||
// Cannot open multiple folders
|
||||
@@ -1252,11 +1278,11 @@ define([
|
||||
break;
|
||||
case 'tree':
|
||||
show = ['open', 'openro', 'openincode', 'expandall', 'collapseall',
|
||||
'color', 'download', 'share', 'savelocal', 'rename', 'delete',
|
||||
'deleteowned', 'removesf', 'properties', 'hashtag'];
|
||||
'color', 'download', 'share', 'savelocal', 'rename', 'delete', 'makeacopy',
|
||||
'deleteowned', 'removesf', 'access', 'properties', 'hashtag'];
|
||||
break;
|
||||
case 'default':
|
||||
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'hashtag'];
|
||||
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy'];
|
||||
break;
|
||||
case 'trashtree': {
|
||||
show = ['empty'];
|
||||
@@ -1664,6 +1690,7 @@ define([
|
||||
&& $target.parents('#cp-app-drive-content')) {
|
||||
newPath = currentPath;
|
||||
}
|
||||
if (newPath[0] !== ROOT) { newPath = [ROOT]; }
|
||||
return newPath;
|
||||
};
|
||||
var onFileDrop = APP.onFileDrop = function (file, e) {
|
||||
@@ -1793,10 +1820,10 @@ define([
|
||||
var $owned = $ownedIcon.clone().appendTo($state);
|
||||
$owned.attr('title', Messages.fm_padIsOwned);
|
||||
$span.addClass('cp-app-drive-element-owned');
|
||||
} else if (data.owners && data.owners.length) {
|
||||
} /* else if (data.owners && data.owners.length) {
|
||||
var $owner = $ownerIcon.clone().appendTo($state);
|
||||
$owner.attr('title', Messages.fm_padIsOwnedOther);
|
||||
}
|
||||
} */
|
||||
};
|
||||
var thumbsUrls = {};
|
||||
var addFileData = function (element, $element) {
|
||||
@@ -1930,6 +1957,44 @@ define([
|
||||
};
|
||||
var getIcon = UI.getIcon;
|
||||
|
||||
var createShareButton = function (id, $container) {
|
||||
var $shareBlock = $('<button>', {
|
||||
'class': 'cp-toolbar-share-button',
|
||||
title: Messages.shareButton
|
||||
});
|
||||
$sharedIcon.clone().appendTo($shareBlock);
|
||||
$('<span>').text(Messages.shareButton).appendTo($shareBlock);
|
||||
var data = manager.getSharedFolderData(id);
|
||||
var parsed = (data.href && data.href.indexOf('#') !== -1) ? Hash.parsePadUrl(data.href) : {};
|
||||
var roParsed = Hash.parsePadUrl(data.roHref) || {};
|
||||
if (!parsed.hash && !roParsed.hash) { return void console.error("Invalid href: "+(data.href || data.roHref)); }
|
||||
var friends = common.getFriends();
|
||||
var ro = folders[id] && folders[id].version >= 2;
|
||||
var modal = UIElements.createShareModal({
|
||||
teamId: APP.team,
|
||||
origin: APP.origin,
|
||||
pathname: "/drive/",
|
||||
friends: friends,
|
||||
title: data.title,
|
||||
password: data.password,
|
||||
sharedFolder: true,
|
||||
common: common,
|
||||
hashes: {
|
||||
editHash: parsed.hash,
|
||||
viewHash: ro && roParsed.hash,
|
||||
}
|
||||
});
|
||||
// If we're a viewer and this is an old shared folder (no read-only mode), we
|
||||
// can't share the read-only URL and we don't have access to the edit one.
|
||||
// We should hide the share button.
|
||||
if (!modal) { return; }
|
||||
$shareBlock.click(function () {
|
||||
UI.openCustomModal(modal);
|
||||
});
|
||||
$container.append($shareBlock);
|
||||
return $shareBlock;
|
||||
};
|
||||
|
||||
// Create the "li" element corresponding to the file/folder located in "path"
|
||||
var createElement = function (path, elPath, root, isFolder) {
|
||||
// Forbid drag&drop inside the trash
|
||||
@@ -1989,6 +2054,13 @@ define([
|
||||
if (invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
$element.find('.fa').on('mouseenter', function (e) {
|
||||
if ($element[0] && $element[0]._tippy) {
|
||||
$element[0]._tippy.destroy();
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
$element.addClass(liClass);
|
||||
var droppable = !isTrash && !APP.$content.data('readOnlyFolder');
|
||||
addDragAndDropHandlers($element, newPath, isFolder, droppable);
|
||||
@@ -2010,6 +2082,15 @@ define([
|
||||
});
|
||||
delete APP.newFolder;
|
||||
}
|
||||
|
||||
if (isSharedFolder && APP.convertedFolder === element) {
|
||||
setTimeout(function () {
|
||||
var $fakeButton = createShareButton(element, $('<div>'));
|
||||
if (!$fakeButton) { return; }
|
||||
$fakeButton.click();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return $element;
|
||||
};
|
||||
|
||||
@@ -2547,43 +2628,6 @@ define([
|
||||
$container.append($block);
|
||||
};
|
||||
|
||||
var createShareButton = function (id, $container) {
|
||||
var $shareBlock = $('<button>', {
|
||||
'class': 'cp-toolbar-share-button',
|
||||
title: Messages.shareButton
|
||||
});
|
||||
$sharedIcon.clone().appendTo($shareBlock);
|
||||
$('<span>').text(Messages.shareButton).appendTo($shareBlock);
|
||||
var data = manager.getSharedFolderData(id);
|
||||
var parsed = (data.href && data.href.indexOf('#') !== -1) ? Hash.parsePadUrl(data.href) : {};
|
||||
var roParsed = Hash.parsePadUrl(data.roHref) || {};
|
||||
if (!parsed.hash && !roParsed.hash) { return void console.error("Invalid href: "+(data.href || data.roHref)); }
|
||||
var friends = common.getFriends();
|
||||
var ro = folders[id] && folders[id].version >= 2;
|
||||
var modal = UIElements.createShareModal({
|
||||
teamId: APP.team,
|
||||
origin: APP.origin,
|
||||
pathname: "/drive/",
|
||||
friends: friends,
|
||||
title: data.title,
|
||||
password: data.password,
|
||||
sharedFolder: true,
|
||||
common: common,
|
||||
hashes: {
|
||||
editHash: parsed.hash,
|
||||
viewHash: ro && roParsed.hash,
|
||||
}
|
||||
});
|
||||
// If we're a viewer and this is an old shared folder (no read-only mode), we
|
||||
// can't share the read-only URL and we don't have access to the edit one.
|
||||
// We should hide the share button.
|
||||
if (!modal) { return; }
|
||||
$shareBlock.click(function () {
|
||||
UI.openCustomModal(modal);
|
||||
});
|
||||
$container.append($shareBlock);
|
||||
};
|
||||
|
||||
var SORT_FOLDER_DESC = 'sortFoldersDesc';
|
||||
var SORT_FILE_BY = 'sortFilesBy';
|
||||
var SORT_FILE_DESC = 'sortFilesDesc';
|
||||
@@ -2718,7 +2762,7 @@ define([
|
||||
});
|
||||
if (keys.length < 2) { return keys; }
|
||||
var mult = asc ? 1 : -1;
|
||||
var getProp = function (el, prop) {
|
||||
var getProp = function (el) {
|
||||
if (folder && root[el] && manager.isSharedFolder(root[el])) {
|
||||
var title = manager.getSharedFolderData(root[el]).title || el;
|
||||
return title.toLowerCase();
|
||||
@@ -2733,13 +2777,19 @@ define([
|
||||
return hrefData.type;
|
||||
}
|
||||
if (prop === 'atime' || prop === 'ctime') {
|
||||
return new Date(data[prop]);
|
||||
return typeof(data[prop]) === "number" ? data[prop] : new Date(data[prop]);
|
||||
}
|
||||
return (manager.getTitle(id) || "").toLowerCase();
|
||||
};
|
||||
var props = {};
|
||||
keys.forEach(function (k) {
|
||||
props[k] = getProp(k);
|
||||
});
|
||||
keys.sort(function(a, b) {
|
||||
if (getProp(a, prop) < getProp(b, prop)) { return mult * -1; }
|
||||
if (getProp(a, prop) > getProp(b, prop)) { return mult * 1; }
|
||||
var _a = props[a];
|
||||
var _b = props[b];
|
||||
if (_a < _b) { return mult * -1; }
|
||||
if (_b > _a) { return mult; }
|
||||
return 0;
|
||||
});
|
||||
return keys;
|
||||
@@ -3034,7 +3084,7 @@ define([
|
||||
$icon.append(getFileIcon(r.id));
|
||||
$type.text(Messages.type[parsed.type] || parsed.type);
|
||||
$title.click(function () {
|
||||
openFile(null, r.data.href);
|
||||
openFile(r.id);
|
||||
});
|
||||
$atimeName.text(Messages.fm_lastAccess);
|
||||
$atime.text(new Date(r.data.atime).toLocaleString());
|
||||
@@ -3051,9 +3101,8 @@ define([
|
||||
}).appendTo($openDir);
|
||||
}
|
||||
$('<a>').text(Messages.fc_prop).click(function () {
|
||||
APP.getProperties(r.id, function (e, $prop) {
|
||||
APP.getProperties(r.id, function (e) {
|
||||
if (e) { return void logError(e); }
|
||||
UI.alert($prop[0], undefined, true);
|
||||
});
|
||||
}).appendTo($openDir);
|
||||
}
|
||||
@@ -3228,21 +3277,23 @@ define([
|
||||
var path = currentPath.slice(1);
|
||||
var root = Util.find(data, path);
|
||||
|
||||
var realPath = [ROOT, SHARED_FOLDER].concat(path);
|
||||
|
||||
if (manager.hasSubfolder(root)) { $list.append($folderHeader); }
|
||||
// display sub directories
|
||||
var keys = Object.keys(root);
|
||||
var sortedFolders = sortElements(true, currentPath, keys, null, !getSortFolderDesc());
|
||||
var sortedFiles = sortElements(false, currentPath, keys, APP.store[SORT_FILE_BY], !getSortFileDesc());
|
||||
var sortedFolders = sortElements(true, realPath, keys, null, !getSortFolderDesc());
|
||||
var sortedFiles = sortElements(false, realPath, keys, APP.store[SORT_FILE_BY], !getSortFileDesc());
|
||||
sortedFolders.forEach(function (key) {
|
||||
if (manager.isFile(root[key])) { return; }
|
||||
var $element = createElement(currentPath, key, root, true);
|
||||
var $element = createElement(realPath, key, root, true);
|
||||
$element.appendTo($list);
|
||||
});
|
||||
if (manager.hasFile(root)) { $list.append($fileHeader); }
|
||||
// display files
|
||||
sortedFiles.forEach(function (key) {
|
||||
if (manager.isFolder(root[key])) { return; }
|
||||
var $element = createElement(currentPath, key, root, false);
|
||||
var $element = createElement(realPath, key, root, false);
|
||||
if (!$element) { return; }
|
||||
$element.appendTo($list);
|
||||
});
|
||||
@@ -3327,7 +3378,9 @@ define([
|
||||
// in history mode we want to focus the version number input
|
||||
if (!history.isHistoryMode && !APP.mobile()) {
|
||||
var st = $tree.scrollTop() || 0;
|
||||
$tree.find('#cp-app-drive-tree-search-input').focus();
|
||||
if (!$('.alertify').length) {
|
||||
$tree.find('#cp-app-drive-tree-search-input').focus();
|
||||
}
|
||||
$tree.scrollTop(st);
|
||||
}
|
||||
$tree.find('#cp-app-drive-tree-search-input')[0].selectionStart = getSearchCursor();
|
||||
@@ -3371,12 +3424,7 @@ define([
|
||||
createNewButton(isInRoot, $toolbar.find('.cp-app-drive-toolbar-leftside'));
|
||||
}
|
||||
if (sfId) {
|
||||
var sfData = manager.getSharedFolderData(sfId);
|
||||
var parsed = Hash.parsePadUrl(sfData.href);
|
||||
sframeChan.event('EV_DRIVE_SET_HASH', parsed.hash || '');
|
||||
createShareButton(sfId, $toolbar.find('.cp-app-drive-toolbar-leftside'));
|
||||
} else {
|
||||
sframeChan.event('EV_DRIVE_SET_HASH', '');
|
||||
}
|
||||
|
||||
|
||||
@@ -3482,6 +3530,9 @@ define([
|
||||
} else {
|
||||
$content.scrollTop(s);
|
||||
}
|
||||
|
||||
delete APP.convertedFolder;
|
||||
|
||||
appStatus.ready(true);
|
||||
};
|
||||
var displayDirectory = APP.displayDirectory = function (path, force) {
|
||||
@@ -3799,12 +3850,11 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var getProperties = APP.getProperties = function (el, cb) {
|
||||
APP.getProperties = function (el, cb) {
|
||||
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
|
||||
return void cb('NOT_FILE');
|
||||
}
|
||||
//var ro = manager.isReadOnlyFile(el);
|
||||
var base = APP.origin;
|
||||
var data;
|
||||
if (manager.isSharedFolder(el)) {
|
||||
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(el)));
|
||||
@@ -3813,47 +3863,47 @@ define([
|
||||
}
|
||||
if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
|
||||
|
||||
if (data.href) {
|
||||
data.href = base + data.href;
|
||||
}
|
||||
if (data.roHref) {
|
||||
data.roHref = base + data.roHref;
|
||||
}
|
||||
|
||||
if (currentPath[0] === TEMPLATE) {
|
||||
data.isTemplate = true;
|
||||
}
|
||||
var opts = {};
|
||||
opts.href = Hash.getRelativeHref(data.href || data.roHref);
|
||||
|
||||
if (manager.isSharedFolder(el)) {
|
||||
var ro = folders[el] && folders[el].version >= 2;
|
||||
if (!ro) { delete data.roHref; }
|
||||
//data.noPassword = true;
|
||||
//data.noEditPassword = true;
|
||||
data.noExpiration = true;
|
||||
// this is here to allow users to check the channel id of a shared folder
|
||||
// we should remove it at some point
|
||||
data.sharedFolder = true;
|
||||
if (!ro) { opts.noReadOnly = true; }
|
||||
}
|
||||
UIElements.getProperties(common, opts, cb);
|
||||
};
|
||||
APP.getAccess = function (el, cb) {
|
||||
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
|
||||
return void cb('NOT_FILE');
|
||||
}
|
||||
var data;
|
||||
if (manager.isSharedFolder(el)) {
|
||||
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(el)));
|
||||
} else {
|
||||
data = JSON.parse(JSON.stringify(manager.getFileData(el)));
|
||||
}
|
||||
if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
|
||||
|
||||
var opts = {};
|
||||
opts.href = Hash.getRelativeHref(data.href || data.roHref);
|
||||
opts.channel = data.channel;
|
||||
|
||||
// Transfer ownership: templates are stored as templates for other users/teams
|
||||
if (currentPath[0] === TEMPLATE) {
|
||||
opts.isTemplate = true;
|
||||
}
|
||||
|
||||
if ((manager.isFile(el) && data.roHref) || manager.isSharedFolder(el)) { // Only for pads!
|
||||
sframeChan.query('Q_GET_PAD_METADATA', {
|
||||
channel: data.channel
|
||||
}, function (err, val) {
|
||||
if (!err && !(val && val.error)) {
|
||||
data.owners = val.owners;
|
||||
data.expire = val.expire;
|
||||
data.pending_owners = val.pending_owners;
|
||||
}
|
||||
UIElements.getProperties(common, data, cb);
|
||||
});
|
||||
return;
|
||||
// Shared folders: no expiration date
|
||||
if (manager.isSharedFolder(el)) {
|
||||
opts.noExpiration = true;
|
||||
}
|
||||
UIElements.getProperties(common, data, cb);
|
||||
|
||||
Access.getAccessModal(common, opts, cb);
|
||||
};
|
||||
|
||||
if (!APP.loggedIn) {
|
||||
$contextMenu.find('.cp-app-drive-context-delete').text(Messages.fc_remove)
|
||||
.attr('data-icon', 'fa-eraser');
|
||||
$contextMenu.find('.cp-app-drive-context-delete').attr('data-icon', faDelete)
|
||||
.html($contextMenu.find('.cp-app-drive-context-remove').html());
|
||||
}
|
||||
var deleteOwnedPaths = function (paths, pathsList) {
|
||||
pathsList = pathsList || [];
|
||||
@@ -3944,15 +3994,41 @@ define([
|
||||
// ANON_SHARED_FOLDER
|
||||
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
|
||||
}
|
||||
var href;
|
||||
if (manager.isPathIn(p.path, [FILES_DATA])) {
|
||||
href = el.roHref;
|
||||
el = p.path[1];
|
||||
} else {
|
||||
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-makeacopy')) {
|
||||
if (paths.length !== 1) { return; }
|
||||
el = manager.find(paths[0].path);
|
||||
var _metadata = manager.getFileData(el);
|
||||
var _simpleData = {
|
||||
title: _metadata.filename || _metadata.title,
|
||||
href: _metadata.href || _metadata.roHref,
|
||||
password: _metadata.password,
|
||||
channel: _metadata.channel,
|
||||
};
|
||||
nThen(function (waitFor) {
|
||||
var path = currentPath;
|
||||
if (path[0] !== ROOT) { path = [ROOT]; }
|
||||
common.sessionStorage.put(Constants.newPadFileData, JSON.stringify(_simpleData), waitFor());
|
||||
common.sessionStorage.put(Constants.newPadPathKey, path, waitFor());
|
||||
common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor());
|
||||
}).nThen(function () {
|
||||
var parsed = Hash.parsePadUrl(_metadata.href || _metadata.roHref);
|
||||
common.openURL(Hash.hashToHref('', parsed.type));
|
||||
// We need to restore sessionStorage for the next time we want to create a pad from this tab
|
||||
// NOTE: the 100ms timeout is to fix a race condition in firefox where sessionStorage
|
||||
// would be deleted before the new tab was created
|
||||
setTimeout(function () {
|
||||
common.sessionStorage.put(Constants.newPadFileData, '', function () {});
|
||||
common.sessionStorage.put(Constants.newPadPathKey, '', function () {});
|
||||
common.sessionStorage.put(Constants.newPadTeamKey, '', function () {});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
else if ($this.hasClass('cp-app-drive-context-openincode')) {
|
||||
@@ -3972,6 +4048,14 @@ define([
|
||||
common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor());
|
||||
}).nThen(function () {
|
||||
common.openURL('/code/');
|
||||
// We need to restore sessionStorage for the next time we want to create a pad from this tab
|
||||
// NOTE: the 100ms timeout is to fix a race condition in firefox where sessionStorage
|
||||
// would be deleted before the new tab was created
|
||||
setTimeout(function () {
|
||||
common.sessionStorage.put(Constants.newPadFileData, '', function () {});
|
||||
common.sessionStorage.put(Constants.newPadPathKey, '', function () {});
|
||||
common.sessionStorage.put(Constants.newPadTeamKey, '', function () {});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4051,8 +4135,7 @@ define([
|
||||
|
||||
if (manager.isFolder(el) && !manager.isSharedFolder(el)) { // Folder
|
||||
// if folder is inside SF
|
||||
return UI.warn('ERROR: Temporarily disabled'); // XXX CONVERT
|
||||
/*if (manager.isInSharedFolder(paths[0].path)) {
|
||||
if (manager.isInSharedFolder(paths[0].path)) {
|
||||
return void UI.alert(Messages.convertFolderToSF_SFParent);
|
||||
}
|
||||
// if folder already contains SF
|
||||
@@ -4080,10 +4163,14 @@ define([
|
||||
if (!res) { return; }
|
||||
var password = $(convertContent).find('#cp-upload-password').val() || undefined;
|
||||
var owned = Util.isChecked($(convertContent).find('#cp-upload-owned'));
|
||||
manager.convertFolderToSharedFolder(paths[0].path, owned, password, refresh);
|
||||
manager.convertFolderToSharedFolder(paths[0].path, owned, password, function (err, obj) {
|
||||
if (err || obj && obj.error) { return void console.error(err || obj.error); }
|
||||
if (obj && obj.fId) { APP.convertedFolder = obj.fId; }
|
||||
refresh();
|
||||
});
|
||||
});
|
||||
}*/
|
||||
} else { // File
|
||||
}
|
||||
} else { // File or shared folder
|
||||
var sf = manager.isSharedFolder(el);
|
||||
data = sf ? manager.getSharedFolderData(el) : manager.getFileData(el);
|
||||
parsed = (data.href && data.href.indexOf('#') !== -1) ? Hash.parsePadUrl(data.href) : {};
|
||||
@@ -4185,9 +4272,19 @@ define([
|
||||
// ANON_SHARED_FOLDER
|
||||
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
|
||||
}
|
||||
getProperties(el, function (e, $prop) {
|
||||
APP.getProperties(el, function (e) {
|
||||
if (e) { return void logError(e); }
|
||||
});
|
||||
}
|
||||
else if ($this.hasClass("cp-app-drive-context-access")) {
|
||||
if (paths.length !== 1) { return; }
|
||||
el = manager.find(paths[0].path);
|
||||
if (paths[0].path[0] === SHARED_FOLDER && APP.newSharedFolder) {
|
||||
// ANON_SHARED_FOLDER
|
||||
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
|
||||
}
|
||||
APP.getAccess(el, function (e) {
|
||||
if (e) { return void logError(e); }
|
||||
UI.openCustomModal($prop[0]);
|
||||
});
|
||||
}
|
||||
else if ($this.hasClass("cp-app-drive-context-hashtag")) {
|
||||
@@ -4538,7 +4635,7 @@ define([
|
||||
onClose: cb
|
||||
});
|
||||
};
|
||||
if (typeof (deprecated) === "object" && APP.editable) {
|
||||
if (typeof (deprecated) === "object" && APP.editable && Object.keys(deprecated).length) {
|
||||
Object.keys(deprecated).forEach(function (fId) {
|
||||
var data = deprecated[fId];
|
||||
var sfId = manager.user.userObject.getSFIdFromHref(data.href);
|
||||
|
||||
1152
www/common/inner/access.js
Normal file
1152
www/common/inner/access.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,9 @@ define([
|
||||
handlers['FRIEND_REQUEST'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
var name = Util.fixHTML(msg.content.displayName) || Messages.anonymous;
|
||||
var userData = msg.content.user || msg.content;
|
||||
var name = Util.fixHTML(userData.displayName) || Messages.anonymous;
|
||||
msg.content = { user: userData };
|
||||
|
||||
// Display the notification
|
||||
content.getFormatText = function () {
|
||||
@@ -37,7 +39,7 @@ define([
|
||||
};
|
||||
|
||||
// Check authenticity
|
||||
if (msg.author !== msg.content.curvePublic) { return; }
|
||||
if (msg.author !== userData.curvePublic) { return; }
|
||||
|
||||
// if not archived, add handlers
|
||||
if (!content.archived) {
|
||||
@@ -51,7 +53,11 @@ define([
|
||||
handlers['FRIEND_REQUEST_ACCEPTED'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
var name = Util.fixHTML(msg.content.name) || Messages.anonymous;
|
||||
var userData = typeof(msg.content.user) === "object" ? msg.content.user : {
|
||||
displayName: msg.content.name,
|
||||
curvePublic: msg.content.user
|
||||
};
|
||||
var name = Util.fixHTML(userData.displayName) || Messages.anonymous;
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('friendRequest_accepted', [name]);
|
||||
};
|
||||
@@ -63,7 +69,11 @@ define([
|
||||
handlers['FRIEND_REQUEST_DECLINED'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg;
|
||||
var name = Util.fixHTML(msg.content.name) || Messages.anonymous;
|
||||
var userData = typeof(msg.content.user) === "object" ? msg.content.user : {
|
||||
displayName: msg.content.name,
|
||||
curvePublic: msg.content.user
|
||||
};
|
||||
var name = Util.fixHTML(userData.displayName) || Messages.anonymous;
|
||||
content.getFormatText = function () {
|
||||
return Messages._getKey('friendRequest_declined', [name]);
|
||||
};
|
||||
|
||||
@@ -62,6 +62,15 @@ body.cp-app-sheet, body.cp-app-oodoc, body.cp-app-ooslide {
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
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 {
|
||||
flex: 1;
|
||||
|
||||
@@ -52,10 +52,10 @@ define([
|
||||
$: $
|
||||
};
|
||||
|
||||
|
||||
var CHECKPOINT_INTERVAL = 50;
|
||||
var DISPLAY_RESTORE_BUTTON = false;
|
||||
var NEW_VERSION = 2;
|
||||
var PENDING_TIMEOUT = 30000;
|
||||
|
||||
var debug = function (x) {
|
||||
if (!window.CP_DEV_MODE) { return; }
|
||||
@@ -76,6 +76,7 @@ define([
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var readOnly = false;
|
||||
var offline = false;
|
||||
var pendingChanges = {};
|
||||
var config = {};
|
||||
var content = {
|
||||
hashes: {},
|
||||
@@ -89,6 +90,7 @@ define([
|
||||
var myUniqueOOId;
|
||||
var myOOId;
|
||||
var sessionId = Hash.createChannelId();
|
||||
var cpNfInner;
|
||||
|
||||
// This structure is used for caching media data and blob urls for each media cryptpad url
|
||||
var mediasData = {};
|
||||
@@ -102,6 +104,18 @@ define([
|
||||
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 && !readOnly) {
|
||||
$('#cp-app-oo-editor').append(h('div#cp-app-oo-offline'));
|
||||
}
|
||||
debug(state);
|
||||
};
|
||||
|
||||
var deleteOffline = function () {
|
||||
var ids = content.ids;
|
||||
var users = Object.keys(metadataMgr.getMetadata().users);
|
||||
@@ -577,15 +591,23 @@ define([
|
||||
var myId = getId();
|
||||
content.locks[myId] = msg;
|
||||
oldLocks = JSON.parse(JSON.stringify(content.locks));
|
||||
// Answer to our onlyoffice
|
||||
send({
|
||||
type: "getLock",
|
||||
locks: getLock()
|
||||
});
|
||||
// Remove old locks
|
||||
deleteOfflineLocks();
|
||||
// Prepare callback
|
||||
if (cpNfInner) {
|
||||
var onPatchSent = function () {
|
||||
cpNfInner.offPatchSent(onPatchSent);
|
||||
// Answer to our onlyoffice
|
||||
send({
|
||||
type: "getLock",
|
||||
locks: getLock()
|
||||
});
|
||||
};
|
||||
cpNfInner.onPatchSent(onPatchSent);
|
||||
}
|
||||
// Commit
|
||||
APP.onLocal();
|
||||
APP.realtime.sync();
|
||||
};
|
||||
|
||||
var parseChanges = function (changes) {
|
||||
@@ -604,13 +626,30 @@ define([
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
var handleChanges = function (obj, send) {
|
||||
// Allow the changes
|
||||
send({
|
||||
type: "unSaveLock",
|
||||
index: ooChannel.cpIndex,
|
||||
time: +new Date()
|
||||
});
|
||||
// Add a new entry to the pendingChanges object.
|
||||
// If we can't send the patch within 30s, force a page reload
|
||||
var uid = Util.uid();
|
||||
pendingChanges[uid] = setTimeout(function () {
|
||||
// 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
|
||||
rtChannel.sendMsg({
|
||||
type: "saveChanges",
|
||||
@@ -619,7 +658,22 @@ define([
|
||||
locks: [content.locks[getId()]],
|
||||
excelAdditionalInfo: null
|
||||
}, 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
|
||||
ooChannel.cpIndex++;
|
||||
ooChannel.lastHash = hash;
|
||||
@@ -663,10 +717,12 @@ define([
|
||||
break;
|
||||
case "isSaveLock":
|
||||
// TODO ping the server to check if we're online first?
|
||||
send({
|
||||
type: "saveLock",
|
||||
saveLock: false
|
||||
});
|
||||
if (!offline) {
|
||||
send({
|
||||
type: "saveLock",
|
||||
saveLock: false
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "getLock":
|
||||
handleLock(obj, send);
|
||||
@@ -752,7 +808,9 @@ define([
|
||||
},
|
||||
"events": {
|
||||
"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
|
||||
'#id-toolbar-full .toolbar-group:nth-child(2), #id-toolbar-full .separator:nth-child(3) { display: none; }' +
|
||||
'#fm-btn-save { display: none !important; }' +
|
||||
@@ -1287,7 +1345,6 @@ define([
|
||||
|
||||
var initializing = true;
|
||||
var $bar = $('#cp-toolbar');
|
||||
var cpNfInner;
|
||||
|
||||
config = {
|
||||
patchTransformer: ChainPad.SmartJSONTransformer,
|
||||
@@ -1304,15 +1361,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var setEditable = function (state) {
|
||||
if (!state) {
|
||||
try {
|
||||
getEditor().setViewModeDisconnect(true);
|
||||
} catch (e) {}
|
||||
}
|
||||
debug(state);
|
||||
};
|
||||
|
||||
var stringifyInner = function () {
|
||||
var obj = {
|
||||
content: content,
|
||||
@@ -1402,11 +1450,20 @@ define([
|
||||
var $exportXLSX = common.createButton('export', true, {}, exportXLSXFile);
|
||||
$exportXLSX.appendTo($rightside);
|
||||
|
||||
var type = common.getMetadataMgr().getPrivateData().ooType;
|
||||
var accept = [".bin", ".ods", ".xlsx"];
|
||||
if (type === "ooslide") {
|
||||
accept = ['.bin', '.odp', '.pptx'];
|
||||
} else if (type === "oodoc") {
|
||||
accept = ['.bin', '.odt', '.docx'];
|
||||
}
|
||||
if (typeof(Atomics) === "undefined") {
|
||||
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);
|
||||
|
||||
if (common.isLoggedIn()) {
|
||||
@@ -1426,6 +1483,8 @@ define([
|
||||
|
||||
var $properties = common.createButton('properties', true);
|
||||
toolbar.$drawer.append($properties);
|
||||
var $access = common.createButton('access', true);
|
||||
toolbar.$drawer.append($access);
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
@@ -1553,25 +1612,23 @@ define([
|
||||
pinImages();
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
// inform of network disconnect
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
UI.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
if (info.state) {
|
||||
UI.findOKButton().click();
|
||||
UI.confirm(Messages.oo_reconnect, function (yes) {
|
||||
if (!yes) { return; }
|
||||
common.gotoURL();
|
||||
});
|
||||
// If we tried to send changes while we were offline, force a page reload
|
||||
UIElements.reconnectAlert();
|
||||
if (Object.keys(pendingChanges).length) {
|
||||
return void UI.confirm(Messages.oo_reconnect, function (yes) {
|
||||
if (!yes) { return; }
|
||||
common.gotoURL();
|
||||
});
|
||||
}
|
||||
setEditable(true);
|
||||
offline = false;
|
||||
} else {
|
||||
setEditable(false);
|
||||
offline = true;
|
||||
UI.findOKButton().click();
|
||||
UI.alert(Messages.common_connectionLost, undefined, true);
|
||||
UIElements.disconnectAlert();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ define([
|
||||
var requireConfig = RequireConfig();
|
||||
|
||||
// Loaded in load #2
|
||||
var hash, href;
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
@@ -19,6 +20,13 @@ define([
|
||||
};
|
||||
window.rc = requireConfig;
|
||||
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',
|
||||
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
|
||||
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
|
||||
@@ -144,6 +152,8 @@ define([
|
||||
});
|
||||
};
|
||||
SFCommonO.start({
|
||||
hash: hash,
|
||||
href: href,
|
||||
type: 'oo',
|
||||
useCreationScreen: true,
|
||||
addData: addData,
|
||||
|
||||
@@ -17,6 +17,7 @@ define([
|
||||
'/common/outer/profile.js',
|
||||
'/common/outer/team.js',
|
||||
'/common/outer/messenger.js',
|
||||
'/common/outer/history.js',
|
||||
'/common/outer/network-config.js',
|
||||
'/customize/application_config.js',
|
||||
|
||||
@@ -28,10 +29,20 @@ define([
|
||||
'/bower_components/saferphore/index.js',
|
||||
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback,
|
||||
Realtime, Messaging, Pinpad,
|
||||
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger,
|
||||
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
|
||||
NetConfig, AppConfig,
|
||||
Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) {
|
||||
|
||||
// Default settings for new users
|
||||
var NEW_USER_SETTINGS = {
|
||||
drive: {
|
||||
hideDuplicate: true
|
||||
},
|
||||
general: {
|
||||
allowUserFeedback: true
|
||||
}
|
||||
};
|
||||
|
||||
var create = function () {
|
||||
var Store = window.Cryptpad_Store = {};
|
||||
var postMessage = function () {};
|
||||
@@ -39,8 +50,6 @@ define([
|
||||
var sendDriveEvent = function () {};
|
||||
var registerProxyEvents = function () {};
|
||||
|
||||
var storeHash, storeChannel;
|
||||
|
||||
var store = window.CryptPad_AsyncStore = {
|
||||
modules: {}
|
||||
};
|
||||
@@ -69,6 +78,7 @@ define([
|
||||
Realtime.whenRealtimeSyncs(s.realtime, waitFor());
|
||||
if (s.sharedFolders && typeof (s.sharedFolders) === "object") {
|
||||
for (var k in s.sharedFolders) {
|
||||
if (!s.sharedFolders[k].realtime) { continue; } // Deprecated
|
||||
Realtime.whenRealtimeSyncs(s.sharedFolders[k].realtime, waitFor());
|
||||
}
|
||||
}
|
||||
@@ -149,13 +159,7 @@ define([
|
||||
};
|
||||
|
||||
var getUserChannelList = function () {
|
||||
// start with your userHash...
|
||||
var userHash = storeHash;
|
||||
if (!userHash) { return null; }
|
||||
|
||||
// No password for drive
|
||||
var secret = Hash.getSecrets('drive', userHash);
|
||||
var userChannel = secret.channel;
|
||||
var userChannel = store.driveChannel;
|
||||
if (!userChannel) { return null; }
|
||||
|
||||
// Get the list of pads' channel ID in your drive
|
||||
@@ -163,7 +167,7 @@ define([
|
||||
// It now includes channels from shared folders
|
||||
var list = store.manager.getChannelsList('pin');
|
||||
|
||||
// Get the avatar
|
||||
// Get the avatar & profile
|
||||
var profile = store.proxy.profile;
|
||||
if (profile) {
|
||||
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
|
||||
@@ -172,6 +176,10 @@ define([
|
||||
if (avatarChan) { list.push(avatarChan); }
|
||||
}
|
||||
|
||||
if (store.proxy.todo) {
|
||||
list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null));
|
||||
}
|
||||
|
||||
if (store.proxy.friends) {
|
||||
var fList = Messaging.getFriendChannelsList(store.proxy);
|
||||
list = list.concat(fList);
|
||||
@@ -304,7 +312,7 @@ define([
|
||||
teamId = data.teamId;
|
||||
}
|
||||
|
||||
if (channel === storeChannel && !force) {
|
||||
if (channel === store.driveChannel && !force) {
|
||||
return void cb({error: 'User drive removal blocked!'});
|
||||
}
|
||||
|
||||
@@ -436,10 +444,12 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
Store.getFileSize = function (clientId, data, cb) {
|
||||
Store.getFileSize = function (clientId, data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
|
||||
|
||||
var channelId = Hash.hrefToHexChannelId(data.href, data.password);
|
||||
var channelId = data.channel || Hash.hrefToHexChannelId(data.href, data.password);
|
||||
store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) {
|
||||
if (e) { return void cb({error: e}); }
|
||||
if (response && response.length && typeof(response[0]) === 'number') {
|
||||
@@ -574,6 +584,7 @@ define([
|
||||
support: Util.find(store.proxy, ['mailboxes', 'support', 'channel']),
|
||||
pendingFriends: store.proxy.friends_pending || {},
|
||||
supportPrivateKey: Util.find(store.proxy, ['mailboxes', 'supportadmin', 'keys', 'curvePrivate']),
|
||||
accountName: store.proxy.login_name || '',
|
||||
teams: teams,
|
||||
plan: account.plan
|
||||
}
|
||||
@@ -703,11 +714,9 @@ define([
|
||||
|
||||
Store.deleteAccount = function (clientId, data, cb) {
|
||||
var edPublic = store.proxy.edPublic;
|
||||
// No password for drive
|
||||
var secret = Hash.getSecrets('drive', storeHash);
|
||||
Store.anonRpcMsg(clientId, {
|
||||
msg: 'GET_METADATA',
|
||||
data: secret.channel
|
||||
data: store.driveChannel
|
||||
}, function (data) {
|
||||
var metadata = data[0];
|
||||
// Owned drive
|
||||
@@ -727,7 +736,7 @@ define([
|
||||
}).nThen(function (waitFor) {
|
||||
// Delete Drive
|
||||
Store.removeOwnedChannel(clientId, {
|
||||
channel: secret.channel,
|
||||
channel: store.driveChannel,
|
||||
force: true
|
||||
}, waitFor());
|
||||
}).nThen(function () {
|
||||
@@ -743,7 +752,7 @@ define([
|
||||
var toSign = {
|
||||
intent: 'Please delete my account.'
|
||||
};
|
||||
toSign.drive = secret.channel;
|
||||
toSign.drive = store.driveChannel;
|
||||
toSign.edPublic = edPublic;
|
||||
var signKey = Crypto.Nacl.util.decodeBase64(store.proxy.edPrivate);
|
||||
var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey);
|
||||
@@ -1016,8 +1025,12 @@ define([
|
||||
|
||||
if (title.trim() === "") { title = UserObject.getDefaultName(p); }
|
||||
|
||||
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
|
||||
if (p.type === "debug") { return void cb(); }
|
||||
if (AppConfig.disableAnonymousStore && !store.loggedIn) {
|
||||
return void cb({ notStored: true });
|
||||
}
|
||||
if (p.type === "debug") {
|
||||
return void cb({ notStored: true });
|
||||
}
|
||||
|
||||
var channelData = Store.channels && Store.channels[channel];
|
||||
|
||||
@@ -1108,7 +1121,7 @@ define([
|
||||
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
|
||||
autoStore: autoStore
|
||||
});
|
||||
return void cb();
|
||||
return void cb({ notStored: true });
|
||||
} else {
|
||||
var roHref;
|
||||
if (h.mode === "view") {
|
||||
@@ -1187,7 +1200,9 @@ define([
|
||||
});
|
||||
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) {
|
||||
var res = {};
|
||||
getAllStores().some(function (s) {
|
||||
@@ -1199,6 +1214,49 @@ define([
|
||||
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)
|
||||
Store.answerFriendRequest = function (clientId, obj, cb) {
|
||||
@@ -1218,15 +1276,15 @@ define([
|
||||
|
||||
// If we accept the request, add the friend to the list
|
||||
if (value) {
|
||||
Messaging.acceptFriendRequest(store, msg.content, function (obj) {
|
||||
Messaging.acceptFriendRequest(store, msg.content.user, function (obj) {
|
||||
if (obj && obj.error) { return void cb(obj); }
|
||||
Messaging.addToFriendList({
|
||||
proxy: store.proxy,
|
||||
realtime: store.realtime,
|
||||
pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
|
||||
}, msg.content, function (err) {
|
||||
}, msg.content.user, function (err) {
|
||||
if (store.messenger) {
|
||||
store.messenger.onFriendAdded(msg.content);
|
||||
store.messenger.onFriendAdded(msg.content.user);
|
||||
}
|
||||
broadcast([], "UPDATE_METADATA");
|
||||
if (err) { return void cb({error: err}); }
|
||||
@@ -1236,12 +1294,7 @@ define([
|
||||
return;
|
||||
}
|
||||
// Otherwise, just remove the notification
|
||||
store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {
|
||||
displayName: store.proxy['cryptpad.username']
|
||||
}, {
|
||||
channel: msg.content.notifications,
|
||||
curvePublic: msg.content.curvePublic
|
||||
}, function (obj) {
|
||||
Messaging.declineFriendRequest(store, msg.content.user, function (obj) {
|
||||
broadcast([], "UPDATE_METADATA");
|
||||
cb(obj);
|
||||
});
|
||||
@@ -1263,8 +1316,9 @@ define([
|
||||
store.proxy.friends_pending[data.curvePublic] = +new Date();
|
||||
broadcast([], "UPDATE_METADATA");
|
||||
|
||||
var myData = Messaging.createData(store.proxy);
|
||||
store.mailbox.sendTo('FRIEND_REQUEST', myData, {
|
||||
store.mailbox.sendTo('FRIEND_REQUEST', {
|
||||
user: Messaging.createData(store.proxy)
|
||||
}, {
|
||||
channel: data.notifications,
|
||||
curvePublic: data.curvePublic
|
||||
}, function (obj) {
|
||||
@@ -1470,11 +1524,39 @@ define([
|
||||
},
|
||||
onError: function (err) {
|
||||
channel.bcast("PAD_ERROR", err);
|
||||
delete channels[data.channel];
|
||||
Store.leavePad(null, data, function () {});
|
||||
},
|
||||
onChannelError: function (err) {
|
||||
channel.bcast("PAD_ERROR", err);
|
||||
delete channels[data.channel];
|
||||
Store.leavePad(null, data, function () {});
|
||||
},
|
||||
onRejected: function (allowed, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
// There is an allow list: check if we can authenticate
|
||||
if (!Array.isArray(allowed)) { return void cb('EINVAL'); }
|
||||
if (!store.loggedIn || !store.proxy.edPublic) { return void cb('EFORBIDDEN'); }
|
||||
var rpc;
|
||||
var teamModule = store.modules['team'];
|
||||
var teams = (teamModule && teamModule.getTeams()) || [];
|
||||
|
||||
if (allowed.indexOf(store.proxy.edPublic) !== -1) {
|
||||
// We are allowed: use our own rpc
|
||||
rpc = store.rpc;
|
||||
} else if (teams.some(function (teamId) {
|
||||
// We're not allowed: check our teams
|
||||
var ed = Util.find(store, ['proxy', 'teams', teamId, 'keys', 'drive', 'edPublic']);
|
||||
if (allowed.indexOf(ed) === -1) { return false; }
|
||||
// This team is allowed: use its rpc
|
||||
var t = teamModule.getTeam(teamId);
|
||||
rpc = t.rpc;
|
||||
return true;
|
||||
})) {}
|
||||
|
||||
if (!rpc) { return void cb('EFORBIDDEN'); }
|
||||
rpc.send('COOKIE', '', function (err) {
|
||||
cb(err);
|
||||
});
|
||||
},
|
||||
onConnectionChange: function (info) {
|
||||
if (!info.state) {
|
||||
@@ -1576,10 +1658,13 @@ define([
|
||||
// data.send === true ==> send the request
|
||||
Store.requestPadAccess = function (clientId, data, cb) {
|
||||
var owner = data.owner;
|
||||
var owners = data.owners;
|
||||
|
||||
// If the owner was not is the pad metadata, check if it is a friend.
|
||||
// We'll contact the first owner for whom we know the mailbox
|
||||
/* // TODO decide whether we want to re-enable this feature for our own contacts
|
||||
// communicate the exception to users that 'muting' won't apply to friends
|
||||
check mailbox in our contacts is not compatible with the new "mute pad" feature
|
||||
var owners = data.owners;
|
||||
if (!owner && Array.isArray(owners)) {
|
||||
var friends = store.proxy.friends || {};
|
||||
// If we have friends, check if an owner is one of them (with a mailbox)
|
||||
@@ -1596,15 +1681,13 @@ define([
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// If send is true, send the request to the owner.
|
||||
if (owner) {
|
||||
if (data.send) {
|
||||
var myData = Messaging.createData(store.proxy);
|
||||
delete myData.channel;
|
||||
store.mailbox.sendTo('REQUEST_PAD_ACCESS', {
|
||||
channel: data.channel,
|
||||
user: myData
|
||||
channel: data.channel
|
||||
}, {
|
||||
channel: owner.notifications,
|
||||
curvePublic: owner.curvePublic
|
||||
@@ -1638,13 +1721,10 @@ define([
|
||||
}
|
||||
})) { return void cb({error: 'ENOTFOUND'}); }
|
||||
|
||||
var myData = Messaging.createData(store.proxy);
|
||||
delete myData.channel;
|
||||
store.mailbox.sendTo("GIVE_PAD_ACCESS", {
|
||||
channel: channel,
|
||||
href: href,
|
||||
title: title,
|
||||
user: myData
|
||||
title: title
|
||||
}, {
|
||||
channel: data.user.notifications,
|
||||
curvePublic: data.user.curvePublic
|
||||
@@ -1678,13 +1758,11 @@ define([
|
||||
}
|
||||
// Tell all the owners that the pad was deleted from the server
|
||||
var curvePublic = store.proxy.curvePublic;
|
||||
var myData = Messaging.createData(store.proxy, false);
|
||||
m.forEach(function (obj) {
|
||||
var mb = JSON.parse(obj);
|
||||
if (mb.curvePublic === curvePublic) { return; }
|
||||
store.mailbox.sendTo('OWNED_PAD_REMOVED', {
|
||||
channel: channel,
|
||||
user: myData
|
||||
channel: channel
|
||||
}, {
|
||||
channel: mb.notifications,
|
||||
curvePublic: mb.curvePublic
|
||||
@@ -1721,13 +1799,19 @@ define([
|
||||
|
||||
// Fetch the latest version of the metadata on the server and return it.
|
||||
// If the pad is stored in our drive, update the local values of "owners" and "expire"
|
||||
Store.getPadMetadata = function (clientId, data, cb) {
|
||||
Store.getPadMetadata = function (clientId, data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
|
||||
store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) {
|
||||
if (err) { return void cb({error: err}); }
|
||||
var metadata = (obj && obj[0]) || {};
|
||||
cb(metadata);
|
||||
|
||||
// If you don't have access to the metadata, stop here
|
||||
// (we can't update the local data)
|
||||
if (metadata.rejected) { return; }
|
||||
|
||||
// Update owners and expire time in the drive
|
||||
getAllStores().forEach(function (s) {
|
||||
var allData = s.manager.findChannel(data.channel, true);
|
||||
@@ -1803,7 +1887,9 @@ define([
|
||||
network.sendto(hk, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
|
||||
};
|
||||
|
||||
Store.getHistory = function (clientId, data, cb) {
|
||||
Store.getHistory = function (clientId, data, _cb, full) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var network = store.network;
|
||||
var hk = network.historyKeeper;
|
||||
|
||||
@@ -1815,6 +1901,8 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var txid = Math.floor(Math.random() * 1000000);
|
||||
|
||||
var msgs = [];
|
||||
var completed = false;
|
||||
var onMsg = function (msg, sender) {
|
||||
@@ -1823,6 +1911,8 @@ define([
|
||||
var parsed = parse(msg);
|
||||
if (!parsed) { return; }
|
||||
|
||||
if (parsed.txid && parsed.txid !== txid) { return; }
|
||||
|
||||
// Ignore the metadata message
|
||||
if (parsed.validateKey && parsed.channel) { return; }
|
||||
if (parsed.error && parsed.channel) {
|
||||
@@ -1843,9 +1933,20 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
msg = parsed[4];
|
||||
if (Array.isArray(parsed) && parsed[0] && parsed[0] !== txid) { return; }
|
||||
|
||||
// Keep only the history for our channel
|
||||
if (parsed[3] !== data.channel) { return; }
|
||||
// If we want the full messages, push the parsed data
|
||||
if (parsed[4] && full) {
|
||||
msgs.push({
|
||||
msg: msg,
|
||||
hash: parsed[4].slice(0,64)
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Otherwise, push the messages
|
||||
msg = parsed[4];
|
||||
if (msg) {
|
||||
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
|
||||
msgs.push(msg);
|
||||
@@ -1854,6 +1955,7 @@ define([
|
||||
network.on('message', onMsg);
|
||||
|
||||
var cfg = {
|
||||
txid: txid,
|
||||
lastKnownHash: data.lastKnownHash
|
||||
};
|
||||
var msg = ['GET_HISTORY', data.channel, cfg];
|
||||
@@ -1905,7 +2007,11 @@ define([
|
||||
first = false;
|
||||
}
|
||||
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
|
||||
msgs.push(msg);
|
||||
msgs.push({
|
||||
msg: msg,
|
||||
author: parsed[2][1],
|
||||
time: parsed[2][5]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1991,6 +2097,8 @@ define([
|
||||
var driveEventClients = [];
|
||||
|
||||
var dropChannel = Store.dropChannel = function (chanId) {
|
||||
console.error('Drop channel', chanId);
|
||||
|
||||
try {
|
||||
store.messenger.leavePad(chanId);
|
||||
} catch (e) { console.error(e); }
|
||||
@@ -2021,9 +2129,13 @@ define([
|
||||
store.onlyoffice.removeClient(clientId);
|
||||
} catch (e) { console.error(e); }
|
||||
try {
|
||||
store.mailbox.removeClient(clientId);
|
||||
if (store.mailbox) {
|
||||
store.mailbox.removeClient(clientId);
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
Object.keys(store.modules).forEach(function (key) {
|
||||
if (!store.modules[key]) { return; }
|
||||
if (!store.modules[key].removeClient) { return; }
|
||||
try {
|
||||
store.modules[key].removeClient(clientId);
|
||||
} catch (e) { console.error(e); }
|
||||
@@ -2095,6 +2207,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', {
|
||||
id: fId,
|
||||
old: o,
|
||||
@@ -2227,7 +2345,8 @@ define([
|
||||
if (!store.loggedIn) { return void cb(); }
|
||||
Store.pinPads(null, data, cb);
|
||||
};
|
||||
if (!proxy.settings) { proxy.settings = {}; }
|
||||
if (!proxy.settings) { proxy.settings = NEW_USER_SETTINGS; }
|
||||
if (!proxy.friends_pending) { proxy.friends_pending = {}; }
|
||||
var manager = store.manager = ProxyManager.create(proxy.drive, {
|
||||
onSync: function (cb) { onSync(null, cb); },
|
||||
edPublic: proxy.edPublic,
|
||||
@@ -2259,7 +2378,6 @@ define([
|
||||
initAnonRpc(null, null, waitFor());
|
||||
initRpc(null, null, waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
loadMailbox(waitFor);
|
||||
Migrate(proxy, waitFor(), function (version, progress) {
|
||||
postMessage(clientId, 'LOADING_DRIVE', {
|
||||
state: (2 + (version / 10)),
|
||||
@@ -2278,6 +2396,8 @@ define([
|
||||
store.messenger = store.modules['messenger'];
|
||||
loadUniversal(Profile, 'profile', waitFor);
|
||||
loadUniversal(Team, 'team', waitFor);
|
||||
loadUniversal(History, 'history', waitFor);
|
||||
loadMailbox(waitFor);
|
||||
cleanFriendRequests();
|
||||
}).nThen(function () {
|
||||
var requestLogin = function () {
|
||||
@@ -2311,13 +2431,7 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
if (!proxy.settings || !proxy.settings.general ||
|
||||
typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
|
||||
proxy.settings = proxy.settings || {};
|
||||
proxy.settings.general = proxy.settings.general || {};
|
||||
proxy.settings.general.allowUserFeedback = true;
|
||||
}
|
||||
returned.feedback = proxy.settings.general.allowUserFeedback;
|
||||
returned.feedback = Util.find(proxy, ['settings', 'general', 'allowUserFeedback']);
|
||||
Feedback.init(returned.feedback);
|
||||
|
||||
if (typeof(cb) === 'function') { cb(returned); }
|
||||
@@ -2379,13 +2493,12 @@ define([
|
||||
|
||||
var connect = function (clientId, data, cb) {
|
||||
var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
|
||||
storeHash = hash;
|
||||
if (!hash) {
|
||||
return void cb({error: '[Store.init] Unable to find or create a drive hash. Aborting...'});
|
||||
}
|
||||
// No password for drive
|
||||
var secret = Hash.getSecrets('drive', hash);
|
||||
storeChannel = secret.channel;
|
||||
store.driveChannel = secret.channel;
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
websocketURL: NetConfig.getWebsocketURL(),
|
||||
|
||||
@@ -60,7 +60,6 @@ define([
|
||||
if (!c) {
|
||||
c = ctx.clients[client] = {
|
||||
channel: channel,
|
||||
padChan: padChan,
|
||||
cursor: {}
|
||||
};
|
||||
} else {
|
||||
@@ -95,6 +94,8 @@ define([
|
||||
ctx.channels[channel] = ctx.channels[channel] || {};
|
||||
|
||||
var chan = ctx.channels[channel];
|
||||
chan.padChan = padChan;
|
||||
|
||||
if (!c.id) { c.id = wc.myID + '-' + client; }
|
||||
if (chan.clients) {
|
||||
// If 2 tabs from the same worker have been opened at the same time,
|
||||
|
||||
246
www/common/outer/history.js
Normal file
246
www/common/outer/history.js
Normal file
@@ -0,0 +1,246 @@
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/userObject.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function (Util, Hash, UserObject, nThen) {
|
||||
var History = {};
|
||||
var commands = {};
|
||||
|
||||
var getAccountChannels = function (ctx) {
|
||||
var channels = [];
|
||||
var edPublic = Util.find(ctx.store, ['proxy', 'edPublic']);
|
||||
|
||||
// Drive
|
||||
var driveOwned = (Util.find(ctx.store, ['driveMetadata', 'owners']) || []).indexOf(edPublic) !== -1;
|
||||
if (driveOwned) {
|
||||
channels.push(ctx.store.driveChannel);
|
||||
}
|
||||
|
||||
// Profile
|
||||
var profile = ctx.store.proxy.profile;
|
||||
if (profile) {
|
||||
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
|
||||
if (profileChan) { channels.push(profileChan); }
|
||||
}
|
||||
|
||||
// Todo
|
||||
if (ctx.store.proxy.todo) {
|
||||
channels.push(Hash.hrefToHexChannelId('/todo/#' + ctx.store.proxy.todo, null));
|
||||
}
|
||||
|
||||
|
||||
// Mailboxes
|
||||
var mailboxes = ctx.store.proxy.mailboxes;
|
||||
if (mailboxes) {
|
||||
var mList = Object.keys(mailboxes).map(function (m) {
|
||||
return {
|
||||
lastKnownHash: mailboxes[m].lastKnownHash,
|
||||
channel: mailboxes[m].channel
|
||||
};
|
||||
});
|
||||
Array.prototype.push.apply(channels, mList);
|
||||
}
|
||||
|
||||
// Shared folders owned by me
|
||||
var sf = ctx.store.proxy[UserObject.SHARED_FOLDERS];
|
||||
if (sf) {
|
||||
var sfChannels = Object.keys(sf).map(function (fId) {
|
||||
var data = sf[fId];
|
||||
if (!data || !data.owners) { return; }
|
||||
var isOwner = Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1;
|
||||
if (!isOwner) { return; }
|
||||
return data.channel;
|
||||
}).filter(Boolean);
|
||||
Array.prototype.push.apply(channels, sfChannels);
|
||||
}
|
||||
|
||||
return channels;
|
||||
};
|
||||
|
||||
var getEdPublic = function (ctx, teamId) {
|
||||
if (!teamId) { return Util.find(ctx.store, ['proxy', 'edPublic']); }
|
||||
|
||||
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||
return Util.find(teamData, ['keys', 'drive', 'edPublic']);
|
||||
};
|
||||
var getRpc = function (ctx, teamId) {
|
||||
if (!teamId) { return ctx.store.rpc; }
|
||||
var teams = ctx.store.modules['team'];
|
||||
if (!teams) { return; }
|
||||
var team = teams.getTeam(teamId);
|
||||
if (!team) { return; }
|
||||
return team.rpc;
|
||||
};
|
||||
|
||||
var getHistoryData = function (ctx, channel, lastKnownHash, teamId, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var edPublic = getEdPublic(ctx, teamId);
|
||||
var Store = ctx.Store;
|
||||
|
||||
var total = 0;
|
||||
var history = 0;
|
||||
var metadata = 0;
|
||||
var hash;
|
||||
nThen(function (waitFor) {
|
||||
// Total size
|
||||
Store.getFileSize(null, {
|
||||
channel: channel
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
return void cb(obj);
|
||||
}
|
||||
if (typeof(obj.size) === "undefined") {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'ENOENT'});
|
||||
}
|
||||
total = obj.size;
|
||||
}));
|
||||
// Pad
|
||||
Store.getHistory(null, {
|
||||
channel: channel,
|
||||
lastKnownHash: lastKnownHash
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
return void cb(obj);
|
||||
}
|
||||
if (!Array.isArray(obj)) {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'EINVAL'});
|
||||
}
|
||||
|
||||
if (!obj.length) { return; }
|
||||
|
||||
hash = obj[0].hash;
|
||||
var messages = obj.map(function(data) {
|
||||
return data.msg;
|
||||
});
|
||||
history = messages.join('\n').length;
|
||||
}), true);
|
||||
// Metadata
|
||||
Store.getPadMetadata(null, {
|
||||
channel: channel
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { return; }
|
||||
if (!obj || typeof(obj) !== "object") { return; }
|
||||
metadata = JSON.stringify(obj).length;
|
||||
if (!obj || !Array.isArray(obj.owners) ||
|
||||
obj.owners.indexOf(edPublic) === -1) {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'INSUFFICIENT_PERMISSIONS'});
|
||||
}
|
||||
}));
|
||||
}).nThen(function () {
|
||||
cb({
|
||||
size: (total - metadata - history),
|
||||
hash: hash
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
commands.GET_HISTORY_SIZE = function (ctx, data, cId, cb) {
|
||||
if (!ctx.store.loggedIn || !ctx.store.rpc) { return void cb({ error: 'INSUFFICIENT_PERMISSIONS' }); }
|
||||
var channels = data.channels;
|
||||
if (!Array.isArray(channels)) { return void cb({ error: 'EINVAL' }); }
|
||||
|
||||
var warning = [];
|
||||
|
||||
// If account trim history, get the correct channels here
|
||||
if (data.account) {
|
||||
channels = getAccountChannels(ctx);
|
||||
}
|
||||
|
||||
var size = 0;
|
||||
var res = [];
|
||||
nThen(function (waitFor) {
|
||||
channels.forEach(function (chan) {
|
||||
var channel = chan;
|
||||
var lastKnownHash;
|
||||
if (typeof (chan) === "object" && chan.channel) {
|
||||
channel = chan.channel;
|
||||
lastKnownHash = chan.lastKnownHash;
|
||||
}
|
||||
getHistoryData(ctx, channel, lastKnownHash, data.teamId, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
warning.push(obj.error);
|
||||
return;
|
||||
}
|
||||
size += obj.size;
|
||||
if (!obj.hash) { return; }
|
||||
res.push({
|
||||
channel: channel,
|
||||
hash: obj.hash
|
||||
});
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
cb({
|
||||
warning: warning.length ? warning : undefined,
|
||||
channels: res,
|
||||
size: size
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
commands.TRIM_HISTORY = function (ctx, data, cId, cb) {
|
||||
if (!ctx.store.loggedIn || !ctx.store.rpc) { return void cb({ error: 'INSUFFICIENT_PERMISSIONS' }); }
|
||||
var channels = data.channels;
|
||||
if (!Array.isArray(channels)) { return void cb({ error: 'EINVAL' }); }
|
||||
|
||||
var rpc = getRpc(ctx, data.teamId);
|
||||
if (!rpc) { return void cb({ error: 'ENORPC'}); }
|
||||
|
||||
var warning = [];
|
||||
|
||||
nThen(function (waitFor) {
|
||||
channels.forEach(function (obj) {
|
||||
rpc.trimHistory(obj, waitFor(function (err) {
|
||||
if (err) {
|
||||
warning.push(err);
|
||||
return;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
// Only one channel and warning: error
|
||||
if (channels.length === 1 && warning.length) {
|
||||
return void cb({error: warning[0]});
|
||||
}
|
||||
cb({
|
||||
warning: warning.length ? warning : undefined
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
History.init = function (cfg, waitFor, emit) {
|
||||
var history = {};
|
||||
if (!cfg.store) { return; }
|
||||
var ctx = {
|
||||
store: cfg.store,
|
||||
Store: cfg.Store,
|
||||
pinPads: cfg.pinPads,
|
||||
updateMetadata: cfg.updateMetadata,
|
||||
emit: emit,
|
||||
};
|
||||
|
||||
history.execCommand = function (clientId, obj, cb) {
|
||||
var cmd = obj.cmd;
|
||||
var data = obj.data;
|
||||
try {
|
||||
commands[cmd](ctx, data, clientId, cb);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return history;
|
||||
};
|
||||
|
||||
return History;
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ define([
|
||||
'/common/common-util.js',
|
||||
], function (Messaging, Hash, Util) {
|
||||
|
||||
// Random timeout between 10 and 30 times your sync time (lag + chainpad sync)
|
||||
var getRandomTimeout = function (ctx) {
|
||||
var lag = ctx.store.realtime.getLag().lag || 0;
|
||||
return (Math.max(0, lag) + 300) * 20 * (0.5 + Math.random());
|
||||
@@ -22,9 +23,11 @@ define([
|
||||
// Store the friend request displayed to avoid duplicates
|
||||
var friendRequest = {};
|
||||
handlers['FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
||||
// Old format: data was stored directly in "content"
|
||||
var userData = data.msg.content.user || data.msg.content;
|
||||
|
||||
// Check if the request is valid (send by the correct user)
|
||||
if (data.msg.author !== data.msg.content.curvePublic) {
|
||||
if (data.msg.author !== userData.curvePublic) {
|
||||
return void cb(true);
|
||||
}
|
||||
|
||||
@@ -40,7 +43,8 @@ define([
|
||||
if (Messaging.getFriend(ctx.store.proxy, data.msg.author) ||
|
||||
ctx.store.proxy.friends_pending[data.msg.author]) {
|
||||
delete ctx.store.proxy.friends_pending[data.msg.author];
|
||||
Messaging.acceptFriendRequest(ctx.store, data.msg.content, function (obj) {
|
||||
|
||||
Messaging.acceptFriendRequest(ctx.store, userData, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
return void cb();
|
||||
}
|
||||
@@ -48,10 +52,10 @@ define([
|
||||
proxy: ctx.store.proxy,
|
||||
realtime: ctx.store.realtime,
|
||||
pinPads: ctx.pinPads
|
||||
}, data.msg.content, function (err) {
|
||||
if (err) { console.error(err); }
|
||||
}, userData, function (err) {
|
||||
if (err) { return void console.error(err); }
|
||||
if (ctx.store.messenger) {
|
||||
ctx.store.messenger.onFriendAdded(data.msg.content);
|
||||
ctx.store.messenger.onFriendAdded(userData);
|
||||
}
|
||||
});
|
||||
ctx.updateMetadata();
|
||||
@@ -63,96 +67,110 @@ define([
|
||||
cb();
|
||||
};
|
||||
removeHandlers['FRIEND_REQUEST'] = function (ctx, box, data) {
|
||||
if (friendRequest[data.content.curvePublic]) {
|
||||
delete friendRequest[data.content.curvePublic];
|
||||
var userData = data.content.user || data.content;
|
||||
if (friendRequest[userData.curvePublic]) {
|
||||
delete friendRequest[userData.curvePublic];
|
||||
}
|
||||
};
|
||||
|
||||
// The DECLINE and ACCEPT messages act on the contacts data
|
||||
// They are processed with a random timeout to avoid having
|
||||
// multiple workers trying to add or remove the contacts at
|
||||
// the same time. Once processed, they are dismissed.
|
||||
// We must dismiss them and send another message to our own
|
||||
// mailbox for the UI part otherwise it would automatically
|
||||
// accept or decline future requests from the same user
|
||||
// until the message is manually dismissed.
|
||||
|
||||
var friendRequestDeclined = {};
|
||||
handlers['DECLINE_FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
||||
setTimeout(function () {
|
||||
// Our friend request was declined.
|
||||
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return; }
|
||||
// Old format: data was stored directly in "content"
|
||||
var userData = data.msg.content.user || data.msg.content;
|
||||
if (!userData.curvePublic) { userData.curvePublic = data.msg.author; }
|
||||
|
||||
// Our friend request was declined.
|
||||
setTimeout(function () {
|
||||
// Only dismissed once in the timeout to make sure we won't lose
|
||||
// the data if we close the worker before adding the friend
|
||||
cb(true);
|
||||
|
||||
// Make sure we really sent it
|
||||
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return; }
|
||||
// Remove the pending message and display the "declined" state in the UI
|
||||
delete ctx.store.proxy.friends_pending[data.msg.author];
|
||||
|
||||
ctx.updateMetadata();
|
||||
if (friendRequestDeclined[data.msg.author]) { return; }
|
||||
friendRequestDeclined[data.msg.author] = true;
|
||||
box.sendMessage({
|
||||
type: 'FRIEND_REQUEST_DECLINED',
|
||||
content: {
|
||||
user: data.msg.author,
|
||||
name: data.msg.content.displayName
|
||||
}
|
||||
}, function () {
|
||||
if (friendRequestDeclined[data.msg.author]) {
|
||||
// TODO remove our message because another one was sent first?
|
||||
}
|
||||
friendRequestDeclined[data.msg.author] = true;
|
||||
});
|
||||
content: { user: userData }
|
||||
}, function () {});
|
||||
}, getRandomTimeout(ctx));
|
||||
cb(true);
|
||||
};
|
||||
// UI for declined friend request
|
||||
handlers['FRIEND_REQUEST_DECLINED'] = function (ctx, box, data, cb) {
|
||||
ctx.updateMetadata();
|
||||
if (friendRequestDeclined[data.msg.content.user]) { return void cb(true); }
|
||||
friendRequestDeclined[data.msg.content.user] = true;
|
||||
var curve = data.msg.content.user.curvePublic || data.msg.content.user;
|
||||
if (friendRequestDeclined[curve]) { return void cb(true); }
|
||||
friendRequestDeclined[curve] = true;
|
||||
cb();
|
||||
};
|
||||
removeHandlers['FRIEND_REQUEST_DECLINED'] = function (ctx, box, data) {
|
||||
if (friendRequestDeclined[data.content.user]) {
|
||||
delete friendRequestDeclined[data.content.user];
|
||||
}
|
||||
var curve = data.content.user.curvePublic || data.content.user;
|
||||
if (friendRequestDeclined[curve]) { delete friendRequestDeclined[curve]; }
|
||||
};
|
||||
|
||||
var friendRequestAccepted = {};
|
||||
handlers['ACCEPT_FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
||||
// Old format: data was stored directly in "content"
|
||||
var userData = data.msg.content.user || data.msg.content;
|
||||
|
||||
// Our friend request was accepted.
|
||||
setTimeout(function () {
|
||||
// Only dismissed once in the timeout to make sure we won't lose
|
||||
// the data if we close the worker before adding the friend
|
||||
cb(true);
|
||||
|
||||
// Make sure we really sent it
|
||||
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return; }
|
||||
// Remove the pending state. It will also us to send a new request in case of error
|
||||
delete ctx.store.proxy.friends_pending[data.msg.author];
|
||||
|
||||
// And add the friend
|
||||
Messaging.addToFriendList({
|
||||
proxy: ctx.store.proxy,
|
||||
realtime: ctx.store.realtime,
|
||||
pinPads: ctx.pinPads
|
||||
}, data.msg.content, function (err) {
|
||||
if (err) { console.error(err); }
|
||||
delete ctx.store.proxy.friends_pending[data.msg.author];
|
||||
if (ctx.store.messenger) {
|
||||
ctx.store.messenger.onFriendAdded(data.msg.content);
|
||||
}
|
||||
}, userData, function (err) {
|
||||
if (err) { return void console.error(err); }
|
||||
// Load the chat if contacts app loaded
|
||||
if (ctx.store.messenger) { ctx.store.messenger.onFriendAdded(userData); }
|
||||
// Update the userlist
|
||||
ctx.updateMetadata();
|
||||
// If you have a profile page open, update it
|
||||
if (ctx.store.modules['profile']) { ctx.store.modules['profile'].update(); }
|
||||
if (friendRequestAccepted[data.msg.author]) { return; }
|
||||
// Display the "accepted" state in the UI
|
||||
if (friendRequestAccepted[data.msg.author]) { return; }
|
||||
friendRequestAccepted[data.msg.author] = true;
|
||||
box.sendMessage({
|
||||
type: 'FRIEND_REQUEST_ACCEPTED',
|
||||
content: {
|
||||
user: data.msg.author,
|
||||
name: data.msg.content.displayName
|
||||
}
|
||||
}, function () {
|
||||
if (friendRequestAccepted[data.msg.author]) {
|
||||
// TODO remove our message because another one was sent first?
|
||||
}
|
||||
friendRequestAccepted[data.msg.author] = true;
|
||||
});
|
||||
content: { user: userData }
|
||||
}, function () {});
|
||||
});
|
||||
}, getRandomTimeout(ctx));
|
||||
cb(true);
|
||||
};
|
||||
// UI for accepted friend request
|
||||
handlers['FRIEND_REQUEST_ACCEPTED'] = function (ctx, box, data, cb) {
|
||||
ctx.updateMetadata();
|
||||
if (friendRequestAccepted[data.msg.content.user]) { return void cb(true); }
|
||||
friendRequestAccepted[data.msg.content.user] = true;
|
||||
var curve = data.msg.content.user.curvePublic || data.msg.content.user;
|
||||
if (friendRequestAccepted[curve]) { return void cb(true); }
|
||||
friendRequestAccepted[curve] = true;
|
||||
cb();
|
||||
};
|
||||
removeHandlers['FRIEND_REQUEST_ACCEPTED'] = function (ctx, box, data) {
|
||||
if (friendRequestAccepted[data.content.user]) {
|
||||
delete friendRequestAccepted[data.content.user];
|
||||
}
|
||||
var curve = data.content.user.curvePublic || data.content.user;
|
||||
if (friendRequestAccepted[curve]) { delete friendRequestAccepted[curve]; }
|
||||
};
|
||||
|
||||
handlers['UNFRIEND'] = function (ctx, box, data, cb) {
|
||||
@@ -476,10 +494,11 @@ define([
|
||||
try {
|
||||
var module = ctx.store.modules['team'];
|
||||
// changeMyRights returns true if we can't change our rights
|
||||
module.changeMyRights(teamId, content.state, content.teamData);
|
||||
module.changeMyRights(teamId, content.state, content.teamData, function (done) {
|
||||
if (!done) { console.error("Can't update team rights"); }
|
||||
cb(true);
|
||||
});
|
||||
} catch (e) { console.error(e); }
|
||||
|
||||
cb(true);
|
||||
};
|
||||
|
||||
handlers['OWNED_PAD_REMOVED'] = function (ctx, box, data, cb) {
|
||||
|
||||
@@ -2,11 +2,12 @@ define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/common-messaging.js',
|
||||
'/common/notify.js',
|
||||
'/common/outer/mailbox-handlers.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
], function (Util, Hash, Realtime, Notify, Handlers, CpNetflux, Crypto) {
|
||||
], function (Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) {
|
||||
var Mailbox = {};
|
||||
|
||||
var TYPES = [
|
||||
@@ -96,6 +97,12 @@ proxy.mailboxes = {
|
||||
|
||||
var crypto = Crypto.Mailbox.createEncryptor(keys);
|
||||
|
||||
// Always send your data
|
||||
if (typeof(msg) === "object" && !msg.user) {
|
||||
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||
msg.user = myData;
|
||||
}
|
||||
|
||||
var text = JSON.stringify({
|
||||
type: type,
|
||||
content: msg
|
||||
@@ -187,6 +194,11 @@ proxy.mailboxes = {
|
||||
history: [], // All the hashes loaded from the server in corretc order
|
||||
content: {}, // Content of the messages that should be displayed
|
||||
sendMessage: function (msg) { // To send a message to our box
|
||||
// Always send your data
|
||||
if (typeof(msg) === "object" && !msg.user) {
|
||||
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||
msg.user = myData;
|
||||
}
|
||||
try {
|
||||
msg = JSON.stringify(msg);
|
||||
} catch (e) {
|
||||
|
||||
@@ -893,7 +893,7 @@ define([
|
||||
};
|
||||
|
||||
var clearOwnedChannel = function (ctx, id, cb) {
|
||||
var channel = ctx.clients[id];
|
||||
var channel = ctx.channels[id];
|
||||
if (!channel) { return void cb({error: 'NO_CHANNEL'}); }
|
||||
if (!ctx.store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
|
||||
ctx.store.rpc.clearOwnedChannel(id, function (err) {
|
||||
|
||||
@@ -12,7 +12,6 @@ define([
|
||||
if (!c) {
|
||||
c = ctx.clients[client] = {
|
||||
channel: channel,
|
||||
padChan: padChan,
|
||||
};
|
||||
} else {
|
||||
return void cb();
|
||||
@@ -45,6 +44,7 @@ define([
|
||||
};
|
||||
|
||||
chan = ctx.channels[channel];
|
||||
chan.padChan = padChan;
|
||||
|
||||
// Create our client ID using the netflux ID
|
||||
if (!c.id) { c.id = wc.myID + '-' + client; }
|
||||
@@ -53,7 +53,7 @@ define([
|
||||
// all our client IDs.
|
||||
if (chan.clients) {
|
||||
chan.clients.forEach(function (cl) {
|
||||
if (ctx.clients[cl] && !ctx.clients[cl].id) {
|
||||
if (ctx.clients[cl]) {
|
||||
ctx.clients[cl].id = wc.myID + '-' + cl;
|
||||
}
|
||||
});
|
||||
@@ -189,15 +189,22 @@ define([
|
||||
if (!c) { return void cb({ error: 'NOT_IN_CHANNEL' }); }
|
||||
var chan = ctx.channels[c.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) {
|
||||
return void chan.sendMsg(data.isCp, cb);
|
||||
return void chan.sendMsg(data.isCp, _cb);
|
||||
}
|
||||
chan.sendMsg(data.msg, cb);
|
||||
ctx.emit('MESSAGE', {
|
||||
msg: data.msg
|
||||
}, chan.clients.filter(function (cl) {
|
||||
return cl !== clientId;
|
||||
}));
|
||||
chan.sendMsg(data.msg, _cb);
|
||||
};
|
||||
|
||||
var reencrypt = function (ctx, data, cId, cb) {
|
||||
|
||||
@@ -214,6 +214,18 @@ define([
|
||||
});
|
||||
} catch (e) {}
|
||||
delete allSharedFolders[secret.channel];
|
||||
// This shouldn't be called on init because we're calling "isNewChannel" first,
|
||||
// but we can still call "cb" just in case. This wait we make sure we won't block
|
||||
// the initial "waitFor"
|
||||
return void cb();
|
||||
}
|
||||
if (info.error === "ERESTRICTED" ) {
|
||||
// This shouldn't happen: allow list are disabled for shared folders
|
||||
// but call "cb" to make sure we won't block the initial "waitFor"
|
||||
sf.teams.forEach(function (obj) {
|
||||
obj.store.manager.restrictedProxy(obj.id, secret.channel);
|
||||
});
|
||||
return void cb();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ define([
|
||||
DISCONNECT: Store.disconnect,
|
||||
CREATE_README: Store.createReadme,
|
||||
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
|
||||
PING: function (cId, data, cb) { cb(); },
|
||||
// RPC
|
||||
UPDATE_PIN_LIMIT: Store.updatePinLimit,
|
||||
GET_PIN_LIMIT: Store.getPinLimit,
|
||||
@@ -50,6 +51,7 @@ define([
|
||||
GET_TEMPLATES: Store.getTemplates,
|
||||
GET_SECURE_FILES_LIST: Store.getSecureFilesList,
|
||||
GET_PAD_DATA: Store.getPadData,
|
||||
GET_PAD_DATA_FROM_CHANNEL: Store.getPadDataFromChannel,
|
||||
GET_STRONGER_HASH: Store.getStrongerHash,
|
||||
INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
|
||||
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', {
|
||||
id: fId,
|
||||
old: o,
|
||||
@@ -903,13 +909,11 @@ define([
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
// Send mailbox to offer ownership
|
||||
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||
ctx.store.mailbox.sendTo("ADD_OWNER", {
|
||||
teamChannel: teamData.channel,
|
||||
chatChannel: Util.find(teamData, ['keys', 'chat', 'channel']),
|
||||
rosterChannel: Util.find(teamData, ['keys', 'roster', 'channel']),
|
||||
title: teamData.metadata.name,
|
||||
user: myData
|
||||
title: teamData.metadata.name
|
||||
}, {
|
||||
channel: user.notifications,
|
||||
curvePublic: user.curvePublic
|
||||
@@ -963,12 +967,10 @@ define([
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
// Send mailbox to offer ownership
|
||||
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||
ctx.store.mailbox.sendTo("RM_OWNER", {
|
||||
teamChannel: teamData.channel,
|
||||
title: teamData.metadata.name,
|
||||
pending: isPendingOwner,
|
||||
user: myData
|
||||
pending: isPendingOwner
|
||||
}, {
|
||||
channel: user.notifications,
|
||||
curvePublic: user.curvePublic
|
||||
@@ -1065,14 +1067,25 @@ define([
|
||||
ctx.emit('ROSTER_CHANGE_RIGHTS', teamId, team.clients);
|
||||
};
|
||||
|
||||
var changeMyRights = function (ctx, teamId, state, data) {
|
||||
if (!teamId) { return true; }
|
||||
var changeMyRights = function (ctx, teamId, state, data, cb) {
|
||||
if (!teamId) { return void cb(false); }
|
||||
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||
if (!teamData) { return true; }
|
||||
if (!teamData) { return void cb(false); }
|
||||
var onReady = ctx.onReadyHandlers[teamId];
|
||||
var team = ctx.teams[teamId];
|
||||
if (!team) { return true; }
|
||||
|
||||
if (teamData.channel !== data.channel || teamData.password !== data.password) { return true; }
|
||||
if (!team && Array.isArray(onReady)) {
|
||||
onReady.push({
|
||||
cb: function () {
|
||||
changeMyRights(ctx, teamId, state, data, cb);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!team) { return void cb(false); }
|
||||
|
||||
if (teamData.channel !== data.channel || teamData.password !== data.password) { return void cb(false); }
|
||||
|
||||
if (state) {
|
||||
teamData.hash = data.hash;
|
||||
@@ -1089,6 +1102,7 @@ define([
|
||||
}
|
||||
|
||||
updateMyRights(ctx, teamId, data.hash);
|
||||
cb(true);
|
||||
};
|
||||
var changeEditRights = function (ctx, teamId, user, state, cb) {
|
||||
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||
@@ -1098,11 +1112,9 @@ define([
|
||||
if (!team) { return void cb ({error: 'ENOENT'}); }
|
||||
|
||||
// Send mailbox to offer ownership
|
||||
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||
ctx.store.mailbox.sendTo("TEAM_EDIT_RIGHTS", {
|
||||
state: state,
|
||||
teamData: getInviteData(ctx, teamId, state),
|
||||
user: myData
|
||||
teamData: getInviteData(ctx, teamId, state)
|
||||
}, {
|
||||
channel: user.notifications,
|
||||
curvePublic: user.curvePublic
|
||||
@@ -1169,7 +1181,6 @@ define([
|
||||
team.roster.add(obj, function (err) {
|
||||
if (err && err !== 'NO_CHANGE') { return void cb({error: err}); }
|
||||
ctx.store.mailbox.sendTo('INVITE_TO_TEAM', {
|
||||
user: Messaging.createData(ctx.store.proxy, false),
|
||||
team: getInviteData(ctx, teamId)
|
||||
}, {
|
||||
channel: user.notifications,
|
||||
@@ -1196,7 +1207,6 @@ define([
|
||||
if (!userData || !userData.notifications) { return cb(); }
|
||||
ctx.store.mailbox.sendTo('KICKED_FROM_TEAM', {
|
||||
pending: data.pending,
|
||||
user: Messaging.createData(ctx.store.proxy, false),
|
||||
teamChannel: getInviteData(ctx, teamId).channel,
|
||||
teamName: getInviteData(ctx, teamId).metadata.name
|
||||
}, {
|
||||
@@ -1634,8 +1644,8 @@ define([
|
||||
});
|
||||
|
||||
};
|
||||
team.changeMyRights = function (id, edit, teamData) {
|
||||
changeMyRights(ctx, id, edit, teamData);
|
||||
team.changeMyRights = function (id, edit, teamData, cb) {
|
||||
changeMyRights(ctx, id, edit, teamData, cb);
|
||||
};
|
||||
team.updateMyData = function (data) {
|
||||
Object.keys(ctx.teams).forEach(function (id) {
|
||||
|
||||
@@ -438,14 +438,24 @@ define([
|
||||
parentEl.push(id);
|
||||
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']);
|
||||
if (path && exp.isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
|
||||
parentEl = exp.find(newPath || [ROOT]);
|
||||
if (filesList.indexOf(id) === -1 && !newPath) {
|
||||
newPath = [ROOT];
|
||||
}
|
||||
// Add to root
|
||||
if (path && exp.isPathIn(newPath, [ROOT])) {
|
||||
parentEl = exp.find(newPath);
|
||||
if (parentEl) {
|
||||
var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
|
||||
parentEl[newName] = id;
|
||||
return;
|
||||
} else {
|
||||
parentEl = exp.find([ROOT]);
|
||||
newPath.slice(1).forEach(function (folderName) {
|
||||
parentEl = parentEl[folderName] = parentEl[folderName] || {};
|
||||
});
|
||||
parentEl[Hash.createChannelId()] = id;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -710,13 +720,17 @@ define([
|
||||
var fixTemplate = function () {
|
||||
if (sharedFolder) { return; }
|
||||
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
|
||||
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
|
||||
var dedup = Util.deduplicateString(files[TEMPLATE]);
|
||||
if (dedup.length !== files[TEMPLATE].length) {
|
||||
files[TEMPLATE] = dedup;
|
||||
}
|
||||
var us = files[TEMPLATE];
|
||||
var rootFiles = exp.getFiles([ROOT]).slice();
|
||||
var rootFiles = exp.getFiles([ROOT]);
|
||||
var toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
if (!exp.isFile(el, true) || rootFiles.indexOf(el) !== -1) {
|
||||
toClean.push(el);
|
||||
return;
|
||||
}
|
||||
if (typeof el === "string") {
|
||||
// We have an old file (href) which is not in filesData: add it
|
||||
@@ -725,6 +739,7 @@ define([
|
||||
href: exp.cryptor.encrypt(el)
|
||||
};
|
||||
us[idx] = id;
|
||||
return;
|
||||
}
|
||||
if (typeof el === "number") {
|
||||
var data = files[FILES_DATA][el];
|
||||
@@ -856,7 +871,7 @@ define([
|
||||
var sf = files[SHARED_FOLDERS];
|
||||
var rootFiles = exp.getFiles([ROOT]);
|
||||
var root = exp.find([ROOT]);
|
||||
var parsed, secret, el;
|
||||
var parsed /*, secret */, el;
|
||||
for (var id in sf) {
|
||||
el = sf[id];
|
||||
id = Number(id);
|
||||
@@ -868,8 +883,7 @@ define([
|
||||
|
||||
// Fix undefined hash
|
||||
parsed = Hash.parsePadUrl(href || el.roHref);
|
||||
secret = Hash.getSecrets('drive', parsed.hash, el.password);
|
||||
if (!secret.keys) {
|
||||
if (!parsed || !parsed.hash || parsed.hash === "undefined") {
|
||||
delete sf[id];
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -89,18 +89,6 @@ var factory = function (Util, Rpc) {
|
||||
});
|
||||
};
|
||||
|
||||
// get the total stored size of a channel's patches (in bytes)
|
||||
exp.getFileSize = function (file, cb) {
|
||||
rpc.send('GET_FILE_SIZE', file, function (e, response) {
|
||||
if (e) { return void cb(e); }
|
||||
if (response && response.length && typeof(response[0]) === 'number') {
|
||||
return void cb(void 0, response[0]);
|
||||
} else {
|
||||
cb('INVALID_RESPONSE');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// get the combined size of all channels (in bytes) for all the
|
||||
// channels which the server has pinned for your publicKey
|
||||
exp.getFileListSize = function (cb) {
|
||||
@@ -137,6 +125,17 @@ var factory = function (Util, Rpc) {
|
||||
});
|
||||
};
|
||||
|
||||
exp.trimHistory = function (data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
if (typeof(data) !== 'object' || !data.channel || !data.hash) {
|
||||
return void cb('INVALID_ARGUMENTS');
|
||||
}
|
||||
rpc.send('TRIM_HISTORY', data, function (e) {
|
||||
if (e) { return cb(e); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
exp.clearOwnedChannel = function (channel, cb) {
|
||||
if (typeof(channel) !== 'string' || channel.length !== 32) {
|
||||
return void cb('INVALID_ARGUMENTS');
|
||||
|
||||
@@ -67,6 +67,13 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var restrictedProxy = function (Env, id) {
|
||||
var lm = { proxy: { deprecated: true } };
|
||||
removeProxy(Env, id);
|
||||
addProxy(Env, id, lm, function () {});
|
||||
return void Env.Store.refreshDriveUI();
|
||||
};
|
||||
|
||||
/*
|
||||
Tools
|
||||
*/
|
||||
@@ -587,14 +594,10 @@ define([
|
||||
|
||||
// convert a folder to a Shared Folder
|
||||
var _convertFolderToSharedFolder = function (Env, data, cb) {
|
||||
return void cb({
|
||||
error: 'DISABLED'
|
||||
}); // XXX CONVERT
|
||||
/*var path = data.path;
|
||||
var path = data.path;
|
||||
var folderElement = Env.user.userObject.find(path);
|
||||
// don't try to convert top-level elements (trash, root, etc) to shared-folders
|
||||
// TODO also validate that you're in root (not templates, etc)
|
||||
if (data.path.length <= 1) {
|
||||
if (path.length <= 1 || path[0] !== UserObject.ROOT) {
|
||||
return void cb({
|
||||
error: 'E_INVAL_PATH',
|
||||
});
|
||||
@@ -664,6 +667,21 @@ define([
|
||||
newPath: newPath,
|
||||
copy: false,
|
||||
}, waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
// Move the owned pads from the old folder to root
|
||||
var paths = [];
|
||||
Object.keys(folderElement).forEach(function (el) {
|
||||
if (!Env.user.userObject.isFile(folderElement[el])) { return; }
|
||||
var data = Env.user.userObject.getFileData(folderElement[el]);
|
||||
if (!data || !_ownedByMe(Env, data.owners)) { return; }
|
||||
// This is an owned pad: move it to ROOT before deleting the initial folder
|
||||
paths.push(path.concat(el));
|
||||
});
|
||||
_move(Env, {
|
||||
paths: paths,
|
||||
newPath: [UserObject.ROOT],
|
||||
copy: false,
|
||||
}, waitFor());
|
||||
}).nThen(function () {
|
||||
// migrate metadata
|
||||
var sharedFolderElement = Env.user.proxy[UserObject.SHARED_FOLDERS][SFId];
|
||||
@@ -678,9 +696,11 @@ define([
|
||||
|
||||
// remove folder
|
||||
Env.user.userObject.delete([path], function () {
|
||||
cb();
|
||||
cb({
|
||||
fId: SFId
|
||||
});
|
||||
});
|
||||
});*/
|
||||
});
|
||||
};
|
||||
|
||||
// Delete permanently some pads or folders
|
||||
@@ -711,6 +731,7 @@ define([
|
||||
// from inside a folder we're trying to delete
|
||||
resolved.main.forEach(function (p) {
|
||||
var el = uo.find(p);
|
||||
if (p[0] === UserObject.FILES_DATA) { return; }
|
||||
if (uo.isFile(el) || uo.isSharedFolder(el)) { return; }
|
||||
var arr = [];
|
||||
uo.getFilesRecursively(el, arr);
|
||||
@@ -771,6 +792,9 @@ define([
|
||||
toUnpin.forEach(function (chan) {
|
||||
if (toKeep.indexOf(chan) === -1) {
|
||||
unpinList.push(chan);
|
||||
|
||||
// Check if need need to restore a full hash (hidden hash deleted from drive)
|
||||
Env.Store.checkDeletedPad(chan);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -783,7 +807,16 @@ define([
|
||||
};
|
||||
// Empty the trash (main drive only)
|
||||
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
|
||||
var _rename = function (Env, data, cb) {
|
||||
@@ -875,10 +908,11 @@ define([
|
||||
cb = cb || function () {};
|
||||
var sfId = Env.user.userObject.getSFIdFromHref(data.href);
|
||||
if (sfId) {
|
||||
var sfData = Env.user.proxy[UserObject.SHARED_FOLDERS][sfId];
|
||||
var sfData = getSharedFolderData(Env, sfId);
|
||||
var sfValue = data.attr ? sfData[data.attr] : JSON.parse(JSON.stringify(sfData));
|
||||
setTimeout(function () {
|
||||
cb(null, {
|
||||
value: sfData[data.attr],
|
||||
value: sfValue,
|
||||
atime: 1
|
||||
});
|
||||
});
|
||||
@@ -1092,6 +1126,7 @@ define([
|
||||
addProxy: callWithEnv(addProxy),
|
||||
removeProxy: callWithEnv(removeProxy),
|
||||
deprecateProxy: callWithEnv(deprecateProxy),
|
||||
restrictedProxy: callWithEnv(restrictedProxy),
|
||||
addSharedFolder: callWithEnv(_addSharedFolder),
|
||||
// Drive
|
||||
command: callWithEnv(onCommand),
|
||||
|
||||
@@ -131,6 +131,8 @@ define([
|
||||
if (state === STATE.INFINITE_SPINNER && newState !== STATE.READY) { return; }
|
||||
if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) {
|
||||
state = newState;
|
||||
} else if (newState === STATE.ERROR) {
|
||||
state = newState;
|
||||
} else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) {
|
||||
throw new Error("Cannot transition from DISCONNECTED to " + newState); // FIXME we are getting "DISCONNECTED to READY" on prod
|
||||
} else if (state !== STATE.READY && newState === STATE.HISTORY_MODE) {
|
||||
@@ -161,6 +163,10 @@ define([
|
||||
}
|
||||
case STATE.ERROR: {
|
||||
evStart.reg(function () {
|
||||
if (text === 'ERESTRICTED') {
|
||||
toolbar.failed(true);
|
||||
return;
|
||||
}
|
||||
toolbar.errorState(true, text);
|
||||
var msg = Messages.chainpadError;
|
||||
UI.errorLoadingScreen(msg, true, true);
|
||||
@@ -171,6 +177,10 @@ define([
|
||||
evStart.reg(function () { toolbar.forgotten(); });
|
||||
break;
|
||||
}
|
||||
case STATE.FORBIDDEN: {
|
||||
evStart.reg(function () { toolbar.deleted(); });
|
||||
break;
|
||||
}
|
||||
case STATE.DELETED: {
|
||||
evStart.reg(function () { toolbar.deleted(); });
|
||||
break;
|
||||
@@ -396,14 +406,18 @@ define([
|
||||
if (state === STATE.DELETED) { return; }
|
||||
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED, info.permanent);
|
||||
/*if (info.state) {
|
||||
UI.findOKButton().click();
|
||||
UIElements.reconnectAlert();
|
||||
} else {
|
||||
UI.alert(Messages.common_connectionLost, undefined, true);
|
||||
UIElements.disconnectAlert();
|
||||
}*/
|
||||
};
|
||||
|
||||
var onError = function (err) {
|
||||
common.onServerError(err, toolbar, function () {
|
||||
common.onServerError(err, null, function () {
|
||||
if (err.type === 'ERESTRICTED') {
|
||||
stateChange(STATE.ERROR, err.type);
|
||||
return;
|
||||
}
|
||||
stateChange(STATE.DELETED);
|
||||
});
|
||||
};
|
||||
@@ -674,6 +688,9 @@ define([
|
||||
$hist.addClass('cp-hidden-if-readonly');
|
||||
toolbar.$drawer.append($hist);
|
||||
|
||||
var $copy = common.createButton('copy', true);
|
||||
toolbar.$drawer.append($copy);
|
||||
|
||||
if (!cpNfInner.metadataMgr.getPrivateData().isTemplate) {
|
||||
var templateObj = {
|
||||
rt: cpNfInner.chainpad,
|
||||
@@ -699,6 +716,8 @@ define([
|
||||
|
||||
var $properties = common.createButton('properties', true);
|
||||
toolbar.$drawer.append($properties);
|
||||
var $access = common.createButton('access', true);
|
||||
toolbar.$drawer.append($access);
|
||||
|
||||
createFilePicker();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ define([
|
||||
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
|
||||
var requireConfig = RequireConfig();
|
||||
|
||||
var hash, href;
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
@@ -18,6 +19,14 @@ define([
|
||||
};
|
||||
window.rc = requireConfig;
|
||||
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',
|
||||
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
|
||||
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
|
||||
@@ -36,6 +45,8 @@ define([
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
SFCommonO.start({
|
||||
hash: hash,
|
||||
href: href,
|
||||
useCreationScreen: true,
|
||||
messaging: true
|
||||
});
|
||||
|
||||
@@ -48,6 +48,8 @@ define([
|
||||
var updateLoadingProgress = config.updateLoadingProgress;
|
||||
config = undefined;
|
||||
|
||||
var evPatchSent = Util.mkEvent();
|
||||
|
||||
var chainpad = ChainPad.create({
|
||||
userName: userName,
|
||||
initialState: initialState,
|
||||
@@ -57,7 +59,10 @@ define([
|
||||
logLevel: logLevel
|
||||
});
|
||||
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 () {
|
||||
onRemote({ realtime: chainpad });
|
||||
@@ -149,6 +154,8 @@ define([
|
||||
metadataMgr: metadataMgr,
|
||||
whenRealtimeSyncs: whenRealtimeSyncs,
|
||||
onInfiniteSpinner: evInfiniteSpinner.reg,
|
||||
onPatchSent: evPatchSent.reg,
|
||||
offPatchSent: evPatchSent.unreg,
|
||||
chainpad: chainpad,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -62,15 +62,16 @@ define([
|
||||
});
|
||||
|
||||
editor._noCursorUpdate = false;
|
||||
editor.state.focused = true;
|
||||
editor.scrollTo(scroll.left, scroll.top);
|
||||
|
||||
if (!editor.hasFocus()) { return; }
|
||||
|
||||
if(selects[0] === selects[1]) {
|
||||
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||
}
|
||||
else {
|
||||
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
|
||||
}
|
||||
|
||||
editor.scrollTo(scroll.left, scroll.top);
|
||||
};
|
||||
|
||||
module.getHeadingText = function (editor) {
|
||||
@@ -121,6 +122,59 @@ define([
|
||||
return text.trim();
|
||||
};
|
||||
|
||||
module.mkIndentSettings = function (editor, metadataMgr) {
|
||||
var setIndentation = function (units, useTabs, fontSize, spellcheck, brackets) {
|
||||
if (typeof(units) !== 'number') { return; }
|
||||
var doc = editor.getDoc();
|
||||
editor.setOption('indentUnit', units);
|
||||
editor.setOption('tabSize', units);
|
||||
editor.setOption('indentWithTabs', useTabs);
|
||||
editor.setOption('spellcheck', spellcheck);
|
||||
editor.setOption('autoCloseBrackets', brackets);
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function() {
|
||||
if (doc.somethingSelected()) {
|
||||
editor.execCommand("indentMore");
|
||||
}
|
||||
else {
|
||||
if (!useTabs) { editor.execCommand("insertSoftTab"); }
|
||||
else { editor.execCommand("insertTab"); }
|
||||
}
|
||||
},
|
||||
"Shift-Tab": function () {
|
||||
editor.execCommand("indentLess");
|
||||
},
|
||||
});
|
||||
setTimeout(function () {
|
||||
$('.CodeMirror').css('font-size', fontSize+'px');
|
||||
editor.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
var indentKey = 'indentUnit';
|
||||
var useTabsKey = 'indentWithTabs';
|
||||
var fontKey = 'fontSize';
|
||||
var spellcheckKey = 'spellcheck';
|
||||
var updateIndentSettings = function () {
|
||||
if (!metadataMgr) { return; }
|
||||
var data = metadataMgr.getPrivateData().settings;
|
||||
data = data.codemirror || {};
|
||||
var indentUnit = data[indentKey];
|
||||
var useTabs = data[useTabsKey];
|
||||
var fontSize = data[fontKey];
|
||||
var spellcheck = data[spellcheckKey];
|
||||
var brackets = data.brackets;
|
||||
setIndentation(
|
||||
typeof(indentUnit) === 'number'? indentUnit : 2,
|
||||
typeof(useTabs) === 'boolean'? useTabs : false,
|
||||
typeof(fontSize) === 'number' ? fontSize : 12,
|
||||
typeof(spellcheck) === 'boolean' ? spellcheck : false,
|
||||
typeof(brackets) === 'boolean' ? brackets : true);
|
||||
};
|
||||
metadataMgr.onChangeLazy(updateIndentSettings);
|
||||
updateIndentSettings();
|
||||
};
|
||||
|
||||
module.create = function (defaultMode, CMeditor) {
|
||||
var exp = {};
|
||||
|
||||
@@ -379,53 +433,7 @@ define([
|
||||
};
|
||||
|
||||
exp.mkIndentSettings = function (metadataMgr) {
|
||||
var setIndentation = function (units, useTabs, fontSize, spellcheck, brackets) {
|
||||
if (typeof(units) !== 'number') { return; }
|
||||
var doc = editor.getDoc();
|
||||
editor.setOption('indentUnit', units);
|
||||
editor.setOption('tabSize', units);
|
||||
editor.setOption('indentWithTabs', useTabs);
|
||||
editor.setOption('spellcheck', spellcheck);
|
||||
editor.setOption('autoCloseBrackets', brackets);
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function() {
|
||||
if (doc.somethingSelected()) {
|
||||
editor.execCommand("indentMore");
|
||||
}
|
||||
else {
|
||||
if (!useTabs) { editor.execCommand("insertSoftTab"); }
|
||||
else { editor.execCommand("insertTab"); }
|
||||
}
|
||||
},
|
||||
"Shift-Tab": function () {
|
||||
editor.execCommand("indentLess");
|
||||
},
|
||||
});
|
||||
$('.CodeMirror').css('font-size', fontSize+'px');
|
||||
};
|
||||
|
||||
var indentKey = 'indentUnit';
|
||||
var useTabsKey = 'indentWithTabs';
|
||||
var fontKey = 'fontSize';
|
||||
var spellcheckKey = 'spellcheck';
|
||||
var updateIndentSettings = function () {
|
||||
if (!metadataMgr) { return; }
|
||||
var data = metadataMgr.getPrivateData().settings;
|
||||
data = data.codemirror || {};
|
||||
var indentUnit = data[indentKey];
|
||||
var useTabs = data[useTabsKey];
|
||||
var fontSize = data[fontKey];
|
||||
var spellcheck = data[spellcheckKey];
|
||||
var brackets = data.brackets;
|
||||
setIndentation(
|
||||
typeof(indentUnit) === 'number'? indentUnit : 2,
|
||||
typeof(useTabs) === 'boolean'? useTabs : false,
|
||||
typeof(fontSize) === 'number' ? fontSize : 12,
|
||||
typeof(spellcheck) === 'boolean' ? spellcheck : false,
|
||||
typeof(brackets) === 'boolean' ? brackets : true);
|
||||
};
|
||||
metadataMgr.onChangeLazy(updateIndentSettings);
|
||||
updateIndentSettings();
|
||||
module.mkIndentSettings(editor, metadataMgr);
|
||||
};
|
||||
|
||||
exp.getCursor = function () {
|
||||
|
||||
@@ -53,11 +53,17 @@ define([
|
||||
|
||||
var $table = File.$table = $('<table>', { id: 'cp-fileupload-table' });
|
||||
|
||||
var hover = false;
|
||||
var createTableContainer = function ($body) {
|
||||
File.$container = $('<div>', { id: 'cp-fileupload' }).append(tableHeader).append($table).appendTo($body);
|
||||
$('.cp-fileupload-header-close').click(function () {
|
||||
File.$container.fadeOut();
|
||||
});
|
||||
File.$container.mouseenter(function () {
|
||||
hover = true;
|
||||
}).mouseleave(function () {
|
||||
hover = false;
|
||||
});
|
||||
return File.$container;
|
||||
};
|
||||
|
||||
@@ -152,6 +158,8 @@ define([
|
||||
});
|
||||
|
||||
onError = function (e) {
|
||||
// TODO if we included the max upload sizes in /api/config
|
||||
// then we could check if a file is too large without going to the server...
|
||||
queue.inProgress = false;
|
||||
queue.next();
|
||||
if (e === 'TOO_LARGE') {
|
||||
@@ -182,13 +190,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var prettySize = function (bytes) {
|
||||
var kB = Util.bytesToKilobytes(bytes);
|
||||
if (kB < 1024) { return kB + Messages.KB; }
|
||||
var mB = Util.bytesToMegabytes(bytes);
|
||||
return mB + Messages.MB;
|
||||
};
|
||||
|
||||
queue.next = function () {
|
||||
if (queue.queue.length === 0) {
|
||||
clearTimeout(queue.to);
|
||||
@@ -209,6 +210,11 @@ define([
|
||||
window.setTimeout(function () { File.$container.show(); });
|
||||
var file = queue.queue.shift();
|
||||
if (file.dl) { return void file.dl(file); }
|
||||
if (file.$line && file.$line[0] && !hover) {
|
||||
var line = file.$line[0];
|
||||
line.scrollIntoView(false);
|
||||
}
|
||||
delete file.$line;
|
||||
upload(file);
|
||||
};
|
||||
queue.push = function (obj) {
|
||||
@@ -224,10 +230,10 @@ define([
|
||||
$('<div>', {'class':'cp-fileupload-table-progressbar'}).appendTo($progressContainer);
|
||||
$('<span>', {'class':'cp-fileupload-table-progress-value'}).text(Messages.upload_pending).appendTo($progressContainer);
|
||||
|
||||
var $tr = $('<tr>', {id: id}).appendTo($table);
|
||||
var $tr = obj.$line = $('<tr>', {id: id}).appendTo($table);
|
||||
var $lines = $table.find('tr[id]');
|
||||
if ($lines.length > 5) {
|
||||
$lines.slice(0, $lines.length - 5).remove();
|
||||
//$lines.slice(0, $lines.length - 5).remove();
|
||||
}
|
||||
|
||||
var $cancel = $('<span>', {'class': 'cp-fileupload-table-cancel-button fa fa-times'}).click(function () {
|
||||
@@ -251,12 +257,19 @@ define([
|
||||
// name
|
||||
$('<td>').append($link).appendTo($tr);
|
||||
// size
|
||||
$('<td>').text(prettySize(estimate)).appendTo($tr);
|
||||
$('<td>').text(UIElements.prettySize(estimate)).appendTo($tr);
|
||||
// progress
|
||||
$('<td>', {'class': 'cp-fileupload-table-progress'}).append($progressContainer).appendTo($tr);
|
||||
// cancel
|
||||
$('<td>', {'class': 'cp-fileupload-table-cancel'}).append($cancel).appendTo($tr);
|
||||
|
||||
var tw = $table.width();
|
||||
var cw = File.$container.prop('clientWidth');
|
||||
var diff = tw - cw;
|
||||
if (diff && diff > 0) {
|
||||
$table.css('margin-right', diff+'px');
|
||||
}
|
||||
|
||||
queue.next();
|
||||
};
|
||||
|
||||
|
||||
@@ -70,7 +70,11 @@ define([
|
||||
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
|
||||
lastKnownHash = data.lastKnownHash;
|
||||
isComplete = data.isFull;
|
||||
Array.prototype.unshift.apply(allMessages, data.messages); // Destructive concat
|
||||
var messages = (data.messages || []).map(function (obj) {
|
||||
return obj.msg;
|
||||
});
|
||||
if (config.debug) { console.log(data.messages); }
|
||||
Array.prototype.unshift.apply(allMessages, messages); // Destructive concat
|
||||
fillChainPad(realtime, allMessages);
|
||||
cb (null, realtime, data.isFull);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/notifications.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
], function ($, Util, UI, UIElements, Notifications, h, Messages) {
|
||||
], function ($, Util, Hash, UI, UIElements, Notifications, h, Messages) {
|
||||
var Mailbox = {};
|
||||
|
||||
Mailbox.create = function (Common) {
|
||||
@@ -53,9 +54,23 @@ define([
|
||||
};
|
||||
var createElement = mailbox.createElement = function (data) {
|
||||
var notif;
|
||||
var avatar;
|
||||
var userData = Util.find(data, ['content', 'msg', 'content', 'user']);
|
||||
if (userData && typeof(userData) === "object" && userData.profile) {
|
||||
avatar = h('span.cp-avatar');
|
||||
Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name);
|
||||
$(avatar).click(function (e) {
|
||||
e.stopPropagation();
|
||||
Common.openURL(Hash.hashToHref(userData.profile, 'profile'));
|
||||
});
|
||||
}
|
||||
notif = h('div.cp-notification', {
|
||||
'data-hash': data.content.hash
|
||||
}, [h('div.cp-notification-content', h('p', formatData(data)))]);
|
||||
}, [
|
||||
avatar,
|
||||
h('div.cp-notification-content',
|
||||
h('p', formatData(data)))
|
||||
]);
|
||||
|
||||
if (typeof(data.content.getFormatText) === "function") {
|
||||
$(notif).find('.cp-notification-content p').html(data.content.getFormatText());
|
||||
|
||||
@@ -30,6 +30,12 @@ define([
|
||||
var password;
|
||||
var initialPathInDrive;
|
||||
|
||||
var currentPad = window.CryptPad_location = {
|
||||
app: '',
|
||||
href: cfg.href || window.location.href,
|
||||
hash: cfg.hash || window.location.hash
|
||||
};
|
||||
|
||||
nThen(function (waitFor) {
|
||||
// Load #2, the loading screen is up so grab whatever you need...
|
||||
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) {
|
||||
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) {
|
||||
var w = waitFor();
|
||||
// No password for drive, profile and todo
|
||||
@@ -171,15 +186,25 @@ define([
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
var parsed = Utils.Hash.parsePadUrl(window.location.href);
|
||||
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) {
|
||||
hashes = h;
|
||||
// Update the rendered hash and the full hash with the "password" settings
|
||||
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;
|
||||
window.onhashchange = function () {};
|
||||
window.location.hash = h.fileHash || h.editHash || h.viewHash || window.location.hash;
|
||||
window.location.href = renderedParsed.getUrl(opts);
|
||||
window.onhashchange = ohc;
|
||||
ohc({reset: true});
|
||||
}
|
||||
@@ -241,13 +266,13 @@ define([
|
||||
if (parsed.type === "file") {
|
||||
// `isNewChannel` doesn't work for files (not a channel)
|
||||
// `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);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 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);
|
||||
};
|
||||
@@ -257,7 +282,47 @@ define([
|
||||
var passwordCfg = {
|
||||
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) {
|
||||
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) {
|
||||
stored = (!err && typeof (data) === "string");
|
||||
}));
|
||||
@@ -273,7 +338,7 @@ define([
|
||||
if (parsed.type === "file") {
|
||||
// `isNewChannel` doesn't work for files (not a channel)
|
||||
// `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(); }
|
||||
// Wrong password or deleted file?
|
||||
askPassword(true, passwordCfg);
|
||||
@@ -281,7 +346,7 @@ define([
|
||||
return;
|
||||
}
|
||||
// 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 (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) {
|
||||
// Error, wrong password stored, the view seed has changed with the password
|
||||
@@ -305,10 +370,12 @@ define([
|
||||
}
|
||||
}).nThen(function (waitFor) {
|
||||
// Check if the pad exists on server
|
||||
if (!window.location.hash) { isNewFile = true; return; }
|
||||
if (!currentPad.hash) { isNewFile = true; return; }
|
||||
|
||||
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); }
|
||||
isNewFile = Boolean(isNew);
|
||||
}));
|
||||
@@ -322,11 +389,12 @@ define([
|
||||
readOnly = false;
|
||||
}
|
||||
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;
|
||||
if (!parsed.type) { throw new Error(); }
|
||||
var defaultTitle = Utils.UserObject.getDefaultName(parsed);
|
||||
var edPublic, curvePublic, notifications, isTemplate;
|
||||
var settings = {};
|
||||
var forceCreationScreen = cfg.useCreationScreen &&
|
||||
sessionStorage[Utils.Constants.displayPadCreationScreen];
|
||||
delete sessionStorage[Utils.Constants.displayPadCreationScreen];
|
||||
@@ -340,9 +408,10 @@ define([
|
||||
edPublic = metaObj.priv.edPublic; // needed to create an owned pad
|
||||
curvePublic = metaObj.user.curvePublic;
|
||||
notifications = metaObj.user.notifications;
|
||||
settings = metaObj.priv.settings;
|
||||
}));
|
||||
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); }
|
||||
isTemplate = t;
|
||||
}));
|
||||
@@ -354,7 +423,7 @@ define([
|
||||
};
|
||||
var additionalPriv = {
|
||||
app: parsed.type,
|
||||
accountName: Utils.LocalStore.getAccountName(),
|
||||
loggedIn: Utils.LocalStore.isLoggedIn(),
|
||||
origin: window.location.origin,
|
||||
pathname: window.location.pathname,
|
||||
fileHost: ApiConfig.fileHost,
|
||||
@@ -368,7 +437,7 @@ define([
|
||||
upgradeURL: Cryptpad.upgradeURL
|
||||
},
|
||||
isNewFile: isNewFile,
|
||||
isDeleted: isNewFile && window.location.hash.length > 0,
|
||||
isDeleted: isNewFile && currentPad.hash.length > 0,
|
||||
forceCreationScreen: forceCreationScreen,
|
||||
password: password,
|
||||
channel: secret.channel,
|
||||
@@ -393,7 +462,7 @@ define([
|
||||
additionalPriv.registeredOnly = true;
|
||||
}
|
||||
|
||||
if (['debug', 'profile'].indexOf(parsed.type) !== -1) {
|
||||
if (['debug', 'profile'].indexOf(currentPad.app) !== -1) {
|
||||
additionalPriv.hashes = hashes;
|
||||
}
|
||||
|
||||
@@ -487,7 +556,7 @@ define([
|
||||
});
|
||||
|
||||
sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) {
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
sessionStorage.redirectTo = currentPad.href;
|
||||
cb();
|
||||
});
|
||||
|
||||
@@ -570,7 +639,18 @@ define([
|
||||
channel: secret.channel,
|
||||
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 === false && window.history && window.history.replaceState) {
|
||||
if (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
window.history.replaceState({}, window.document.title, hash);
|
||||
}
|
||||
}
|
||||
cb({error: err});
|
||||
});
|
||||
});
|
||||
@@ -580,6 +660,9 @@ define([
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -595,6 +678,17 @@ define([
|
||||
forceSave: true
|
||||
};
|
||||
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 === false && window.history && window.history.replaceState) {
|
||||
if (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
window.history.replaceState({}, window.document.title, hash);
|
||||
}
|
||||
}
|
||||
cb({error: err});
|
||||
});
|
||||
});
|
||||
@@ -689,6 +783,10 @@ define([
|
||||
Utils.LocalStore.logout(cb);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_LOGOUT_EVERYWHERE', function (data, cb) {
|
||||
Cryptpad.logoutFromAll(Utils.Util.bake(Utils.LocalStore.logout, cb));
|
||||
});
|
||||
|
||||
sframeChan.on('EV_NOTIFY', function (data) {
|
||||
Notifier.notify(data);
|
||||
});
|
||||
@@ -707,6 +805,20 @@ define([
|
||||
Cryptpad.saveAsTemplate(Cryptget.put, data, cb);
|
||||
});
|
||||
|
||||
sframeChan.on('EV_MAKE_A_COPY', function () {
|
||||
var data = {
|
||||
channel: secret.channel,
|
||||
href: currentPad.href,
|
||||
password: password,
|
||||
title: currentTitle
|
||||
};
|
||||
sessionStorage[Utils.Constants.newPadFileData] = JSON.stringify(data);
|
||||
window.open(window.location.pathname);
|
||||
setTimeout(function () {
|
||||
delete sessionStorage[Utils.Constants.newPadFileData];
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Messaging
|
||||
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (data, cb) {
|
||||
Cryptpad.messaging.sendFriendRequest(data, cb);
|
||||
@@ -765,10 +877,14 @@ define([
|
||||
}, function (data) {
|
||||
cb({
|
||||
isFull: data.isFull,
|
||||
messages: data.messages.map(function (msg) {
|
||||
messages: data.messages.map(function (obj) {
|
||||
// The 3rd parameter "true" means we're going to skip signature validation.
|
||||
// We don't need it since the message is already validated serverside by hk
|
||||
return crypto.decrypt(msg, true, true);
|
||||
return {
|
||||
msg: crypto.decrypt(obj.msg, true, true),
|
||||
author: obj.author,
|
||||
time: obj.time
|
||||
};
|
||||
}),
|
||||
lastKnownHash: data.lastKnownHash
|
||||
});
|
||||
@@ -801,15 +917,19 @@ define([
|
||||
|
||||
// Present mode URL
|
||||
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);
|
||||
});
|
||||
sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) {
|
||||
var parsed = Utils.Hash.parsePadUrl(window.location.href);
|
||||
window.location.href = parsed.getUrl({
|
||||
embed: parsed.hashData.embed,
|
||||
present: data
|
||||
});
|
||||
// Update the rendered hash and the full hash with the "present" settings
|
||||
var opts = parsed.getOptions();
|
||||
opts.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 +1131,7 @@ define([
|
||||
});
|
||||
|
||||
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) {
|
||||
sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) {
|
||||
if (obj && obj.cancel) { cb(); }
|
||||
@@ -1027,12 +1147,12 @@ define([
|
||||
});
|
||||
|
||||
sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
|
||||
data.href = data.href || window.location.href;
|
||||
data.href = data.href;
|
||||
Cryptpad.changeOOPassword(data, cb);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
|
||||
data.href = data.href || window.location.href;
|
||||
data.href = data.href;
|
||||
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
|
||||
});
|
||||
|
||||
@@ -1134,22 +1254,24 @@ define([
|
||||
});
|
||||
// REQUEST_ACCESS is used both to check IF we can contact an owner (send === false)
|
||||
// AND also to send the request if we want (send === true)
|
||||
sframeChan.on('Q_REQUEST_ACCESS', function (send, cb) {
|
||||
sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
|
||||
if (readOnly && hashes.editHash) {
|
||||
return void cb({error: 'ALREADYKNOWN'});
|
||||
}
|
||||
var send = data.send;
|
||||
var metadata = data.metadata;
|
||||
var owner, owners;
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
var _secret = secret;
|
||||
if (metadata && metadata.roHref) {
|
||||
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
|
||||
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
|
||||
}
|
||||
var crypto = Crypto.createEncryptor(_secret.keys);
|
||||
nThen(function (waitFor) {
|
||||
// Try to get the owner's mailbox from the pad metadata first.
|
||||
// If it's is an older owned pad, check if the owner is a friend
|
||||
// or an acquaintance (from async-store directly in requestAccess)
|
||||
Cryptpad.getPadMetadata({
|
||||
channel: secret.channel
|
||||
}, waitFor(function (obj) {
|
||||
obj = obj || {};
|
||||
if (obj.error) { return; }
|
||||
|
||||
var todo = function (obj) {
|
||||
owners = obj.owners;
|
||||
|
||||
var mailbox;
|
||||
@@ -1168,22 +1290,102 @@ define([
|
||||
owner = data;
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
};
|
||||
|
||||
// If we already have metadata, use it, otherwise, try to get it
|
||||
if (metadata) { return void todo(metadata); }
|
||||
|
||||
Cryptpad.getPadMetadata({
|
||||
channel: _secret.channel
|
||||
}, waitFor(function (obj) {
|
||||
obj = obj || {};
|
||||
if (obj.error) { return; }
|
||||
todo(obj);
|
||||
}));
|
||||
}).nThen(function () {
|
||||
// If we are just checking (send === false) and there is a mailbox field, cb state true
|
||||
// If there is no mailbox, we'll have to check if an owner is a friend in the worker
|
||||
if (owner && !send) {
|
||||
return void cb({state: true});
|
||||
}
|
||||
if (!send) { return void cb({state: Boolean(owner)}); }
|
||||
|
||||
Cryptpad.padRpc.requestAccess({
|
||||
send: send,
|
||||
channel: secret.channel,
|
||||
channel: _secret.channel,
|
||||
owner: owner,
|
||||
owners: owners
|
||||
}, cb);
|
||||
});
|
||||
});
|
||||
|
||||
// Add or remove our mailbox from the list if we're an owner
|
||||
sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) {
|
||||
var metadata = data.metadata;
|
||||
var add = data.add;
|
||||
var _secret = secret;
|
||||
if (metadata && (metadata.href || metadata.roHref)) {
|
||||
var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
|
||||
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
|
||||
}
|
||||
var crypto = Crypto.createEncryptor(_secret.keys);
|
||||
nThen(function (waitFor) {
|
||||
// If we already have metadata, use it, otherwise, try to get it
|
||||
if (metadata) { return; }
|
||||
|
||||
Cryptpad.getPadMetadata({
|
||||
channel: secret.channel
|
||||
}, waitFor(function (obj) {
|
||||
obj = obj || {};
|
||||
if (obj.error) {
|
||||
waitFor.abort();
|
||||
return void cb(obj);
|
||||
}
|
||||
metadata = obj;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
// Get and maybe migrate the existing mailbox object
|
||||
var owners = metadata.owners;
|
||||
if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
|
||||
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
|
||||
}
|
||||
|
||||
// Remove a mailbox
|
||||
if (!add) {
|
||||
// Old format: this is the mailbox of the first owner
|
||||
if (typeof (metadata.mailbox) === "string" && metadata.mailbox) {
|
||||
// Not our mailbox? abort
|
||||
if (owners[0] !== edPublic) {
|
||||
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
|
||||
}
|
||||
// Remove it
|
||||
return void Cryptpad.setPadMetadata({
|
||||
channel: _secret.channel,
|
||||
command: 'RM_MAILBOX',
|
||||
value: []
|
||||
}, cb);
|
||||
} else if (metadata.mailbox) { // New format
|
||||
return void Cryptpad.setPadMetadata({
|
||||
channel: _secret.channel,
|
||||
command: 'RM_MAILBOX',
|
||||
value: [edPublic]
|
||||
}, cb);
|
||||
}
|
||||
return void cb({
|
||||
error: 'NO_MAILBOX'
|
||||
});
|
||||
}
|
||||
// Add a mailbox
|
||||
var toAdd = {};
|
||||
toAdd[edPublic] = crypto.encrypt(JSON.stringify({
|
||||
notifications: notifications,
|
||||
curvePublic: curvePublic
|
||||
}));
|
||||
Cryptpad.setPadMetadata({
|
||||
channel: _secret.channel,
|
||||
command: 'ADD_MAILBOX',
|
||||
value: toAdd
|
||||
}, cb);
|
||||
});
|
||||
});
|
||||
|
||||
sframeChan.on('EV_BURN_PAD', function (channel) {
|
||||
if (!burnAfterReading) { return; }
|
||||
Cryptpad.burnPad({
|
||||
@@ -1227,14 +1429,26 @@ define([
|
||||
}
|
||||
} 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
|
||||
var rtStarted = false;
|
||||
var startRealtime = function (rtConfig) {
|
||||
rtConfig = rtConfig || {};
|
||||
rtStarted = true;
|
||||
|
||||
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 (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
window.history.replaceState({}, window.document.title, hash);
|
||||
@@ -1250,7 +1464,7 @@ define([
|
||||
Cryptpad.padRpc.onReadyEvent.reg(function () {
|
||||
Cryptpad.burnPad({
|
||||
password: password,
|
||||
href: window.location.href,
|
||||
href: currentPad.href,
|
||||
channel: secret.channel,
|
||||
ownerKey: burnAfterReading
|
||||
});
|
||||
@@ -1265,7 +1479,7 @@ define([
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
onConnect: function () {
|
||||
if (window.location.hash && window.location.hash !== '#') {
|
||||
if (currentPad.hash && currentPad.hash !== '#') {
|
||||
/*window.location = parsed.getUrl({
|
||||
present: parsed.hashData.present,
|
||||
embed: parsed.hashData.embed
|
||||
@@ -1278,11 +1492,11 @@ define([
|
||||
};
|
||||
|
||||
nThen(function (waitFor) {
|
||||
if (isNewFile && cfg.owned && !window.location.hash) {
|
||||
if (isNewFile && cfg.owned && !currentPad.hash) {
|
||||
Cryptpad.getMetadata(waitFor(function (err, m) {
|
||||
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");
|
||||
sframeChan.onReady(function () {
|
||||
sframeChan.query("EV_LOADING_ERROR", "DELETED");
|
||||
@@ -1309,11 +1523,13 @@ define([
|
||||
var ohc = window.onhashchange;
|
||||
window.onhashchange = function () {};
|
||||
window.location.hash = newHash;
|
||||
currentPad.hash = newHash;
|
||||
currentPad.href = '/' + parsed.type + '/#' + newHash;
|
||||
window.onhashchange = ohc;
|
||||
ohc({reset: true});
|
||||
|
||||
// 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);
|
||||
hashes = Utils.Hash.getHashes(secret);
|
||||
readOnly = false;
|
||||
|
||||
@@ -69,7 +69,7 @@ define([
|
||||
funcs.getAppConfig = function () { return AppConfig; };
|
||||
|
||||
funcs.isLoggedIn = function () {
|
||||
return ctx.metadataMgr.getPrivateData().accountName;
|
||||
return ctx.metadataMgr.getPrivateData().loggedIn;
|
||||
};
|
||||
|
||||
// MISC
|
||||
@@ -83,6 +83,9 @@ define([
|
||||
};
|
||||
|
||||
// UI
|
||||
window.CryptPad_UI = UI;
|
||||
window.CryptPad_UIElements = UIElements;
|
||||
window.CryptPad_common = funcs;
|
||||
funcs.createUserAdminMenu = callWithCommon(UIElements.createUserAdminMenu);
|
||||
funcs.initFilePicker = callWithCommon(UIElements.initFilePicker);
|
||||
funcs.openFilePicker = callWithCommon(UIElements.openFilePicker);
|
||||
@@ -139,7 +142,13 @@ define([
|
||||
if (!$mt || !$mt.is('media-tag')) { return; }
|
||||
var chanStr = $mt.attr('src');
|
||||
var keyStr = $mt.attr('data-crypto-key');
|
||||
var channel = chanStr.replace(/\/blob\/[0-9a-f]{2}\//i, '');
|
||||
// Remove origin
|
||||
var a = document.createElement('a');
|
||||
a.href = chanStr;
|
||||
var src = a.pathname;
|
||||
// Get channel id
|
||||
var channel = src.replace(/\/blob\/[0-9a-f]{2}\//i, '');
|
||||
// Get key
|
||||
var key = keyStr.replace(/cryptpad:/i, '');
|
||||
var metadata = $mt[0]._mediaObject._blob.metadata;
|
||||
ctx.sframeChan.query('Q_IMPORT_MEDIATAG', {
|
||||
@@ -603,6 +612,10 @@ define([
|
||||
|
||||
UI.addTooltips();
|
||||
|
||||
ctx.sframeChan.on("EV_PAD_NODATA", function () {
|
||||
UI.errorLoadingScreen(Messages.safeLinks_error);
|
||||
});
|
||||
|
||||
ctx.sframeChan.on("EV_PAD_PASSWORD", function (cfg) {
|
||||
UIElements.displayPasswordPrompt(funcs, cfg);
|
||||
});
|
||||
|
||||
@@ -166,14 +166,14 @@ MessengerUI, Messages) {
|
||||
});
|
||||
};
|
||||
var showColors = false;
|
||||
var updateUserList = function (toolbar, config) {
|
||||
var updateUserList = function (toolbar, config, forceOffline) {
|
||||
if (!config.displayed || config.displayed.indexOf('userlist') === -1) { return; }
|
||||
// Make sure the elements are displayed
|
||||
var $userButtons = toolbar.userlist;
|
||||
var $userlistContent = toolbar.userlistContent;
|
||||
|
||||
var metadataMgr = config.metadataMgr;
|
||||
var online = metadataMgr.isConnected();
|
||||
var online = !forceOffline && metadataMgr.isConnected();
|
||||
var userData = metadataMgr.getMetadata().users;
|
||||
var viewers = metadataMgr.getViewers();
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
@@ -574,6 +574,7 @@ MessengerUI, Messages) {
|
||||
return $shareBlock;
|
||||
};
|
||||
|
||||
/*
|
||||
var createRequest = function (toolbar, config) {
|
||||
if (!config.metadataMgr) {
|
||||
throw new Error("You must provide a `metadataMgr` to display the request access button");
|
||||
@@ -590,13 +591,13 @@ MessengerUI, Messages) {
|
||||
// If we have access to the owner's mailbox, display the button and enable it
|
||||
// false => check if we can contact the owner
|
||||
// true ==> send the request
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', false, function (err, obj) {
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:false}, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
var locked = false;
|
||||
$requestBlock.show().click(function () {
|
||||
if (locked) { return; }
|
||||
locked = true;
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', true, function (err, obj) {
|
||||
Common.getSframeChannel().query('Q_REQUEST_ACCESS', {send:true}, function (err, obj) {
|
||||
if (obj && obj.state) {
|
||||
UI.log(Messages.requestEdit_sent);
|
||||
$requestBlock.hide();
|
||||
@@ -614,6 +615,7 @@ MessengerUI, Messages) {
|
||||
|
||||
return $requestBlock;
|
||||
};
|
||||
*/
|
||||
|
||||
var createTitle = function (toolbar, config) {
|
||||
var $titleContainer = $('<span>', {
|
||||
@@ -1226,7 +1228,7 @@ MessengerUI, Messages) {
|
||||
tb['fileshare'] = createFileShare;
|
||||
tb['title'] = createTitle;
|
||||
tb['pageTitle'] = createPageTitle;
|
||||
tb['request'] = createRequest;
|
||||
//tb['request'] = createRequest;
|
||||
tb['lag'] = $.noop;
|
||||
tb['spinner'] = createSpinner;
|
||||
tb['state'] = $.noop;
|
||||
@@ -1258,12 +1260,15 @@ MessengerUI, Messages) {
|
||||
initClickEvents(toolbar, config);
|
||||
initNotifications(toolbar, config);
|
||||
|
||||
var failed = toolbar.failed = function () {
|
||||
var failed = toolbar.failed = function (hideUserList) {
|
||||
toolbar.connected = false;
|
||||
|
||||
if (toolbar.spinner) {
|
||||
toolbar.spinner.text(Messages.disconnected);
|
||||
}
|
||||
if (hideUserList) {
|
||||
updateUserList(toolbar, config, true);
|
||||
}
|
||||
//checkLag(toolbar, config);
|
||||
};
|
||||
toolbar.initializing = function (/*userId*/) {
|
||||
@@ -1310,7 +1315,7 @@ MessengerUI, Messages) {
|
||||
toolbar.deleted = function (/*userId*/) {
|
||||
toolbar.isErrorState = true;
|
||||
toolbar.connected = false;
|
||||
updateUserList(toolbar, config);
|
||||
updateUserList(toolbar, config, true);
|
||||
if (toolbar.spinner) {
|
||||
toolbar.spinner.text(Messages.deletedFromServer);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"media": "Multimèdia",
|
||||
"todo": "Tasques",
|
||||
"contacts": "Contactes",
|
||||
"sheet": "Full (Beta)",
|
||||
"sheet": "Full de càlcul",
|
||||
"teams": "Equips"
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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...",
|
||||
@@ -181,7 +181,6 @@
|
||||
"okButton": "D'acord (Enter)",
|
||||
"cancel": "Cancel·la",
|
||||
"cancelButton": "Cancel·la (Esc)",
|
||||
"doNotAskAgain": "No ho preguntis més (Esc)",
|
||||
"show_help_button": "Mostra l'ajuda",
|
||||
"hide_help_button": "Amaga l'ajuda",
|
||||
"help_button": "Ajuda",
|
||||
@@ -299,7 +298,7 @@
|
||||
"contacts_confirmRemoveHistory": "De debò voleu suprimir permanentment el vostre historial de xat? Les dades no es podran restaurar",
|
||||
"contacts_removeHistoryServerError": "Hi ha hagut un error mentre es suprimia el vostre historial del xat. Torneu-ho a provar",
|
||||
"contacts_fetchHistory": "Recupera l'historial antic",
|
||||
"contacts_friends": "Amistats",
|
||||
"contacts_friends": "Contactes",
|
||||
"contacts_rooms": "Sales",
|
||||
"contacts_leaveRoom": "Deixa aquesta sala",
|
||||
"contacts_online": "En aquesta sala hi ha una altra persona en línia",
|
||||
@@ -531,5 +530,94 @@
|
||||
"settings_padSpellcheckTitle": "Correcció ortogràfica",
|
||||
"settings_padSpellcheckHint": "Aquesta opció us permet habilitar la correcció ortogràfica als documents de text. Les errades es subratllaran en vermell i haureu de mantenir apretada la tecla Ctrl o Meta mentre cliqueu el botó dret per veure les opcions correctes.",
|
||||
"settings_padSpellcheckLabel": "Activa la correcció ortogràfica",
|
||||
"settings_creationSkip": "Salta la pantalla de creació de document"
|
||||
"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": "Milloreu el compte",
|
||||
"settings_ownDriveHint": "Degut a raons tècniques els comptes antics no tenen accés a les darreres funcionalitats . Una actualització gratuïta habilitarà les funcionalitats actuals i prepararà el vostre CryptDrive per futures actualitzacions.",
|
||||
"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",
|
||||
"home_ngi": "Guanyador del premi NGI",
|
||||
"home_host_agpl": "CryptPad es distribueix sota els termes de la llicència de programari AGPL3",
|
||||
"home_host": "Aquesta és una instància comunitària independent de CryptPad. El codi font està disponible<a href=\"https://github.com/xwiki-labs/cryptpad\" target=\"_blank\" rel=\"noreferrer noopener\">a GitHub</a>.",
|
||||
"home_product": "CryptPad és una alternativa, respectuosa amb la privacitat, a les utilitats d'oficina i els serveis al núvol. Tot el contingut desat a CryptPad es xifra abans de ser enviat, això vol dir que ningú pot accedir a les vostres dades sense que li doneu les claus (fins i tot nosaltres).",
|
||||
"mdToolbar_toc": "Taula de continguts",
|
||||
"mdToolbar_code": "Codi",
|
||||
"mdToolbar_check": "Llista de tasques",
|
||||
"mdToolbar_list": "Llista de vinyetes",
|
||||
"mdToolbar_nlist": "Llista ordenada",
|
||||
"mdToolbar_quote": "Cita",
|
||||
"mdToolbar_link": "Enllaç",
|
||||
"mdToolbar_heading": "Capçalera",
|
||||
"mdToolbar_strikethrough": "Tatxat",
|
||||
"mdToolbar_italic": "Cursiva",
|
||||
"mdToolbar_bold": "Negreta",
|
||||
"mdToolbar_tutorial": "https://ca.wikipedia.org/wiki/Markdown",
|
||||
"mdToolbar_help": "Suport",
|
||||
"mdToolbar_defaultText": "El vostre text",
|
||||
"mdToolbar_button": "Mostra o amaga la barra d'eines de Markdown",
|
||||
"pad_base64": "Aquest document conté imatges emmagatzemades de forma ineficient. Aquestes imatges augmenten significativament la mida del document al CryptDrive i fa que la càrrega sigui més lenta. Podeu migrar les imatges a un format diferent perquè es guardin per separat al CryptDrive. Voleu migrar ara aquestes imatges?",
|
||||
"todo_markAsIncompleteTitle": "Marca la tasca com incompleta",
|
||||
"pad_hideToolbar": "Amaga la barra d'eines",
|
||||
"pad_showToolbar": "Mostra la barra d'eines",
|
||||
"todo_removeTaskTitle": "Elimina la tasca del llistat",
|
||||
"todo_markAsCompleteTitle": "Marca la tasca com a completada",
|
||||
"todo_newTodoNameTitle": "Afegiu la tasca al llistat",
|
||||
"todo_newTodoNamePlaceholder": "Descriviu la tasca...",
|
||||
"todo_title": "CryptTasques",
|
||||
"download_step2": "Desxifrant",
|
||||
"download_step1": "Descarregant",
|
||||
"download_dl": "Descarrega",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"media": "Medien",
|
||||
"todo": "Aufgaben",
|
||||
"contacts": "Kontakte",
|
||||
"sheet": "Tabelle (Beta)",
|
||||
"sheet": "Tabelle",
|
||||
"teams": "Teams"
|
||||
},
|
||||
"button_newpad": "Neues Rich-Text-Pad",
|
||||
@@ -92,7 +92,7 @@
|
||||
"exportButtonTitle": "Exportiere dieses Pad in eine lokale Datei",
|
||||
"exportPrompt": "Wie möchtest du die Datei nennen?",
|
||||
"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_accountName": "Kontoname",
|
||||
"clickToEdit": "Zum Bearbeiten klicken",
|
||||
@@ -179,7 +179,6 @@
|
||||
"okButton": "OK (Enter)",
|
||||
"cancel": "Abbrechen",
|
||||
"cancelButton": "Abbrechen (Esc)",
|
||||
"doNotAskAgain": "Nicht mehr fragen (Esc)",
|
||||
"show_help_button": "Hilfe anzeigen",
|
||||
"hide_help_button": "Hilfe verbergen",
|
||||
"help_button": "Hilfe",
|
||||
@@ -275,11 +274,11 @@
|
||||
"profile_description": "Beschreibung",
|
||||
"profile_fieldSaved": "Neuer Wert gespeichert: {0}",
|
||||
"profile_viewMyProfile": "Mein Profil anzeigen",
|
||||
"userlist_addAsFriendTitle": "Benutzer \"{0}\" eine Freundschaftsanfrage senden",
|
||||
"userlist_addAsFriendTitle": "Benutzer \"{0}\" eine Kontaktanfrage senden",
|
||||
"contacts_title": "Kontakte",
|
||||
"contacts_addError": "Fehler bei dem Hinzufügen des Kontakts zur Liste",
|
||||
"contacts_added": "Verbindungseinladung angenommen.",
|
||||
"contacts_rejected": "Verbindungseinladung abgelehnt",
|
||||
"contacts_added": "Kontaktanfrage akzeptiert.",
|
||||
"contacts_rejected": "Kontaktanfrage abgelehnt",
|
||||
"contacts_request": "Benutzer <em>{0}</em> möchte dich als Kontakt hinzufügen. <b>Annehmen<b>?",
|
||||
"contacts_send": "Senden",
|
||||
"contacts_remove": "Diesen Kontakt entfernen",
|
||||
@@ -344,7 +343,7 @@
|
||||
"fm_info_root": "Erstelle hier so viele Ordner, wie du willst, um deine Dateien und Dokumente zu organisieren.",
|
||||
"fm_info_unsorted": "Hier sind alle besuchte Dateien enthalten, die noch nicht in \"Dokumente\" einsortiert oder in den Papierkorb verschoben wurden.",
|
||||
"fm_info_template": "Hier sind alle Dokumente enthalten, die als Vorlage gespeichert wurden und die du wiederverwenden kannst, um ein neues Pad zu erstellen.",
|
||||
"fm_info_recent": "Hier werden die zuletzt geöffneten Dokumente aufgelistet.",
|
||||
"fm_info_recent": "Diese Pads wurden kürzlich von dir oder von Personen, mit denen du zusammenarbeitest, geöffnet oder geändert.",
|
||||
"fm_info_trash": "Leere den Papierkorb, um mehr freien Platz in deinem CryptDrive zu erhalten.",
|
||||
"fm_info_allFiles": "Beinhaltet alle Dateien von \"Dokumente\", \"Unsortierte Dateien\" und \"Papierkorb\". Dateien können hier nicht verschoben oder entfernt werden.",
|
||||
"fm_info_anonymous": "Du bist nicht eingeloggt, daher laufen die Pads nach 3 Monaten aus (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">mehr dazu lesen</a>). Der Zugang zu den Pads ist in deinem Browser gespeichert, daher wird das Löschen des Browserverlaufs sie möglicherweise verschwinden lassen.<br><a href=\"/register/\">Registriere dich</a> oder <a href=\"/login/\">logge dich ein</a>, um sie dauerhaft zugänglich zu machen.<br>",
|
||||
@@ -494,8 +493,8 @@
|
||||
"settings_pinningError": "Etwas ging schief",
|
||||
"settings_usageAmount": "Deine gepinnten Pads belegen {0} MB",
|
||||
"settings_logoutEverywhereButton": "Ausloggen",
|
||||
"settings_logoutEverywhereTitle": "Überall ausloggen",
|
||||
"settings_logoutEverywhere": "Das Ausloggen in allen andere Websitzungen erzwingen",
|
||||
"settings_logoutEverywhereTitle": "Andere Sitzungen beenden",
|
||||
"settings_logoutEverywhere": "Das Ausloggen aus allen anderen Websitzungen erzwingen",
|
||||
"settings_logoutEverywhereConfirm": "Bist du sicher? Du wirst dich auf allen deinen Geräten wieder einloggen müssen.",
|
||||
"settings_driveDuplicateTitle": "Duplizierte eigene Pads",
|
||||
"settings_driveDuplicateHint": "Wenn du ein eigenes Pad in einem geteilten Ordner verschiebst, wird eine Kopie in deinem CryptDrive behalten, damit du die Kontrolle des Dokuments nicht verlierst. Du kannst duplizierte Dateien verbergen. Nur die Version in dem geteilten Ordner wird dann angezeigt, außer sie wurde gelöscht. In diesem Fall, wird sie wieder angezeigt.",
|
||||
@@ -515,8 +514,8 @@
|
||||
"settings_creationSkipFalse": "Anzeigen",
|
||||
"settings_templateSkip": "Wahl der Vorlage überspringen",
|
||||
"settings_templateSkipHint": "Wenn du ein neues Pad erstellst und passende Vorlagen vorhanden sind, erscheint ein Dialog zur Auswahl einer Vorlage. Hier kannst du diesen Dialog überspringen und somit keine Vorlage verwenden.",
|
||||
"settings_ownDriveTitle": "Aktiviere die neuesten Funktionen für dein Konto",
|
||||
"settings_ownDriveHint": "Aus technischen Gründen sind nicht alle neue Funktionen für ältere Konten verfügbar. Ein kostenloses Upgrade wird dein CryptDrive für zukünftige Funktionen vorbereiten, ohne deine Arbeit zu stören.",
|
||||
"settings_ownDriveTitle": "Account aktualisieren",
|
||||
"settings_ownDriveHint": "Aus technischen Gründen sind nicht alle neue Funktionen für ältere Konten verfügbar. Eine kostenlose Aktualisierung wird die neuen Funktionen aktivieren und dein CryptDrive für zukünftige Aktualisierungen vorbereiten.",
|
||||
"settings_ownDriveButton": "Upgrade deines Kontos",
|
||||
"settings_ownDriveConfirm": "Das Upgrade deines Kontos kann einige Zeit dauern. Du wirst dich auf allen Geräten neu einloggen müssen. Bist du sicher?",
|
||||
"settings_ownDrivePending": "Das Upgrade deines Kontos läuft. Bitte schließe die Seite nicht und lade sie nicht neu, bis dieser Vorgang abgeschlossen ist.",
|
||||
@@ -548,7 +547,7 @@
|
||||
"upload_success": "Deine Datei ({0}) wurde erfolgreich hochgeladen und zu deinem CryptDrive hinzugefügt.",
|
||||
"upload_notEnoughSpace": "Der verfügbare Speicherplatz in deinem CryptDrive reicht leider nicht für diese Datei.",
|
||||
"upload_notEnoughSpaceBrief": "Unzureichender Speicherplatz",
|
||||
"upload_tooLarge": "Diese Datei ist zu groß, um hochgeladen zu werden.",
|
||||
"upload_tooLarge": "Diese Datei überschreitet die für deinen Account erlaubte maximale Größe.",
|
||||
"upload_tooLargeBrief": "Datei zu groß",
|
||||
"upload_choose": "Eine Datei wählen",
|
||||
"upload_pending": "In der Warteschlange",
|
||||
@@ -664,7 +663,7 @@
|
||||
"features_f_social": "Soziale Anwendungen",
|
||||
"features_f_social_note": "Ein Profil erstellen, ein Profilbild verwenden, mit Kontakten chatten",
|
||||
"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_note": "Dateien in deinem CryptDrive werden nicht wegen Inaktivität gelöscht",
|
||||
"features_f_register": "Registrieren (kostenlos)",
|
||||
@@ -767,7 +766,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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"remove": {
|
||||
@@ -799,7 +798,7 @@
|
||||
"title": "Andere Fragen",
|
||||
"pay": {
|
||||
"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": {
|
||||
"q": "Was ist euer Ziel?",
|
||||
@@ -860,7 +859,7 @@
|
||||
"colors": "Ändere Text- und Hintergrundfarbe mit den Schaltflächen <span class=\"fa fa-i-cursor\"></span> und <span class=\"fa fa-square\"></span>"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
@@ -871,14 +870,14 @@
|
||||
"embed": "Bette Bilder von deiner Festplatte <span class=\"fa fa-file-image-o\"></span> oder von deinem CryptDrive <span class=\"fa fa-image\"></span> ein und exportiere sie als PNG zu deiner Festplatte <span class=\"fa fa-download\"></span> oder zu deinem CryptDrive <span class=\"fa fa-cloud-upload\"></span>"
|
||||
},
|
||||
"kanban": {
|
||||
"add": "Füge ein neues Board hinzu mit der Schaltfläche <span class=\"fa fa-plus\"></span> in der rechten oberen Ecke",
|
||||
"task": "Verschiebe Einträge von einem Board zum anderen per Drag & Drop",
|
||||
"color": "Ändere die Farben durch Klicken auf den farbigen Teil neben den Boardtiteln"
|
||||
"add": "Füge neue Karten und Boards mit der Schaltfläche <span class=\"fa fa-plus\"></span> hinzu",
|
||||
"task": "Verschiebe Einträge per Drag & Drop oder ziehe sie zum <span class=\"fa fa-trash\"></span> Papierkorb, um sie zu löschen",
|
||||
"color": "Bearbeite Titel, Inhalt, Tags und Farben durch Klicken auf die Schaltfläche <span class=\"fa fa-pencil\"></span> neben den Titeln von Karten und Boards"
|
||||
}
|
||||
},
|
||||
"driveReadmeTitle": "Was ist 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_cat1": "Lerne dein CryptDrive kennen",
|
||||
"readme_cat1_l1": "Ein Pad erstellen: Klicke in deinem CryptDrive auf {0} und dann auf {1}.",
|
||||
@@ -911,7 +910,7 @@
|
||||
"feedback_about": "Wenn du das liest, fragst du dich wahrscheinlich, weshalb dein Browser bei der der Ausführung mancher Aktionen Anfragen an Webseiten sendet",
|
||||
"feedback_privacy": "Wir respektieren deine Datenschutz, aber gleichzeitig wollen wir, dass die Benutzung von CryptPad sehr leicht ist. Deshalb wollen wir erfahren, welche Funktion am wichtigsten für unsere Benutzer ist, indem wir diese mit einer genauen Parameterbeschreibung anfordern.",
|
||||
"feedback_optout": "Wenn du das nicht möchtest, kannst du es in <a href='/settings/'>deinen Einstellungen</a> deaktivieren",
|
||||
"creation_404": "Dieses Pad existiert nicht mehr. Benutze das folgende Formular, um ein neues Pad zu gestalten.",
|
||||
"creation_404": "Dieses Pad existiert nicht mehr. Benutze das folgende Formular, um ein neues Pad zu erstellen.",
|
||||
"creation_ownedTitle": "Pad-Typ",
|
||||
"creation_owned": "Eigenes Pad",
|
||||
"creation_ownedTrue": "Eigenes Pad",
|
||||
@@ -931,7 +930,6 @@
|
||||
"creation_noTemplate": "Keine Vorlage",
|
||||
"creation_newTemplate": "Neue Vorlage",
|
||||
"creation_create": "Erstellen",
|
||||
"creation_saveSettings": "Dieses Dialog nicht mehr anzeigen",
|
||||
"creation_settings": "Mehr Einstellungen anzeigen",
|
||||
"creation_rememberHelp": "Gehe zu deinen Einstellungen, um diese Auswahl zurückzusetzen",
|
||||
"creation_owners": "Eigentümer",
|
||||
@@ -998,7 +996,6 @@
|
||||
"crowdfunding_popup_text": "<h3>Wir brauchen deine Hilfe!</h3>Um sicherzustellen, dass CryptPad weiter aktiv entwickelt wird, unterstütze bitte das Projekt über die <a href=\"https://opencollective.com/cryptpad\">OpenCollective Seite</a>, wo du unsere <b>Roadmap</b> und <b>Funding-Ziele</b> lesen kannst.",
|
||||
"crowdfunding_popup_yes": "OpenCollective besuchen",
|
||||
"crowdfunding_popup_no": "Nicht jetzt",
|
||||
"crowdfunding_popup_never": "Nicht mehr darum bitten",
|
||||
"invalidHashError": "Das angeforderte Dokument hat eine ungültige URL.",
|
||||
"oo_cantUpload": "Das Hochladen von Dateien ist nicht erlaubt, während andere Nutzer anwesend sind.",
|
||||
"oo_uploaded": "Das Hochladen wurde abgeschlossen. Klicke auf OK zum Neuladen der Seite oder auf Abbrechen zum Fortfahren im schreibgeschützten Modus.",
|
||||
@@ -1053,18 +1050,17 @@
|
||||
"friendRequest_later": "Später entscheiden",
|
||||
"friendRequest_accept": "Akzeptieren (Enter)",
|
||||
"friendRequest_decline": "Ablehnen",
|
||||
"friendRequest_declined": "<b>{0}</b> hat deine Freundschaftsanfrage abgelehnt",
|
||||
"friendRequest_accepted": "<b>{0}</b> hat deine Freundschaftsanfrage akzeptiert",
|
||||
"friendRequest_received": "<b>{0}</b> möchte mit dir befreundet sein",
|
||||
"friendRequest_notification": "<b>{0}</b> hat dir eine Freundschaftsanfrage geschickt",
|
||||
"friendRequest_declined": "<b>{0}</b> hat deine Kontaktanfrage abgelehnt",
|
||||
"friendRequest_accepted": "<b>{0}</b> hat deine Kontaktanfrage akzeptiert",
|
||||
"friendRequest_received": "<b>{0}</b> möchte dein Kontakt sein",
|
||||
"friendRequest_notification": "<b>{0}</b> hat dir eine Kontaktanfrage geschickt",
|
||||
"notifications_empty": "Keine Benachrichtigungen verfügbar",
|
||||
"notifications_title": "Du hast ungelesene Benachrichtigungen",
|
||||
"profile_addDescription": "Beschreibung hinzufügen",
|
||||
"profile_editDescription": "Deine Beschreibung bearbeiten",
|
||||
"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_friendRequestSent": "Freundschaftsanfrage gesendet...",
|
||||
"profile_friend": "{0} ist mit dir befreundet",
|
||||
"profile_friendRequestSent": "Kontaktanfrage gesendet...",
|
||||
"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_folderShared": "{0} hat einen Ordner mit dir geteilt: <b>{1}</b>",
|
||||
@@ -1075,7 +1071,7 @@
|
||||
"share_deselectAll": "Alle abwählen",
|
||||
"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.",
|
||||
"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_collapseAll": "Alle einklappen",
|
||||
"fc_color": "Farbe ändern",
|
||||
@@ -1100,7 +1096,7 @@
|
||||
"support_formMessage": "Gib deine Nachricht ein...",
|
||||
"support_cat_tickets": "Vorhandene 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, aber du kannst ein neues Ticket eröffnen. Du kannst geschlossene Tickets ausblenden.",
|
||||
"support_answer": "Antworten",
|
||||
"support_close": "Ticket schließen",
|
||||
"support_remove": "Ticket entfernen",
|
||||
@@ -1111,7 +1107,7 @@
|
||||
"notificationsPage": "Benachrichtigungen",
|
||||
"openNotificationsApp": "Benachrichtigungspanel öffnen",
|
||||
"notifications_cat_all": "Alle",
|
||||
"notifications_cat_friends": "Freundschaftsanfragen",
|
||||
"notifications_cat_friends": "Kontaktanfragen",
|
||||
"notifications_cat_pads": "Mit mir geteilt",
|
||||
"notifications_cat_archived": "Verlauf",
|
||||
"notifications_dismissAll": "Alle verbergen",
|
||||
@@ -1119,8 +1115,6 @@
|
||||
"requestEdit_button": "Bearbeitungsrechte anfragen",
|
||||
"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_fromFriend": "Du bist mit {0} befreundet",
|
||||
"requestEdit_fromStranger": "Du bist <b>nicht</b> mit {0} befreundet",
|
||||
"requestEdit_viewPad": "Pad in neuem Tab öffnen",
|
||||
"later": "Später entscheiden",
|
||||
"requestEdit_request": "{1} möchte das Pad <b>{0}</b> bearbeiten",
|
||||
@@ -1147,15 +1141,15 @@
|
||||
"register_emailWarning1": "Wenn du möchtest, kannst du dies tun. Allerdings wird sie nicht an unseren Server gesendet.",
|
||||
"register_emailWarning2": "Du kannst dein Passwort nicht wie bei vielen anderen Diensten mit der E-Mail-Adresse zurücksetzen.",
|
||||
"register_emailWarning3": "Wenn du dies verstanden hast und die E-Mail-Adresse dennoch als Benutzername verwenden möchtest, klicke auf OK.",
|
||||
"owner_unknownUser": "Unbekannter Benutzer",
|
||||
"owner_unknownUser": "unbekannt",
|
||||
"owner_removeButton": "Ausgewählte Eigentümer entfernen",
|
||||
"owner_removeConfirm": "Bist du sicher, dass die Eigentümerschaft der ausgewählten Benutzer entfernen möchtest? Sie werden über diese Aktion informiert.",
|
||||
"owner_removeMeConfirm": "Du bist dabei, deine Rechte als Eigentümer aufzugeben. Diese Aktion kannst du nicht rückgängig machen. Bist du sicher?",
|
||||
"owner_openModalButton": "Eigentümer verwalten",
|
||||
"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_removePendingText": "Eine ausstehende Einladung zurückziehen",
|
||||
"owner_addText": "Einen Freund zur Mit-Eigentümerschaft einladen",
|
||||
"owner_removeText": "Eigentümer",
|
||||
"owner_removePendingText": "Ausstehend",
|
||||
"owner_addText": "Einen Kontakt zur Mit-Eigentümerschaft einladen",
|
||||
"owner_removePendingButton": "Ausgewählte Einladungen zurückziehen",
|
||||
"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?",
|
||||
@@ -1166,8 +1160,7 @@
|
||||
"owner_removedPending": "{0} hat die Einladung zur Eigentümerschaft von <b>{1}</b> zurückgezogen",
|
||||
"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.",
|
||||
"team_pickFriends": "Kontakte auswählen, um sie in dieses Team einzuladen",
|
||||
"team_pcsSelectLabel": "Speichern in",
|
||||
"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>",
|
||||
@@ -1189,7 +1182,7 @@
|
||||
"team_rosterPromote": "Befördern",
|
||||
"team_rosterDemote": "Degradieren",
|
||||
"team_rosterKick": "Aus dem Team entfernen",
|
||||
"team_inviteButton": "Freunde einladen",
|
||||
"team_inviteButton": "Kontakte einladen",
|
||||
"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_owner": "Eigentümer",
|
||||
@@ -1294,5 +1287,54 @@
|
||||
"oo_exportInProgress": "Export wird durchgeführt",
|
||||
"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_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",
|
||||
"settings_cat_security": "Vertraulichkeit",
|
||||
"settings_safeLinksHint": "CryptPad fügt den Pad-Links die Schlüssel zum Entschlüsseln der Inhalte hinzu. Jeder, der Zugriff auf den Browserverlauf hat, kann möglicherweise die Daten lesen. Dazu gehören Browsererweiterungen und Browser, die den Verlauf geräteübergreifend synchronisieren. Die Aktivierung von \"sicheren Links\" verhindert, dass die Schlüssel in den Browserverlauf gelangen oder in der Adressleiste angezeigt werden, wann immer dies möglich ist. Wir empfehlen dringend, diese Funktion zu aktivieren und das Menü {0} Teilen zu verwenden.",
|
||||
"dontShowAgain": "Nicht mehr anzeigen",
|
||||
"profile_login": "Du musst dich einloggen, um diesen Benutzer zu deinen Kontakten hinzuzufügen",
|
||||
"safeLinks_error": "Dieser Link gibt dir keinen Zugriff auf das Dokument",
|
||||
"settings_safeLinksCheckbox": "Sichere Links aktivieren",
|
||||
"settings_safeLinksTitle": "Sichere Links",
|
||||
"settings_trimHistoryHint": "Spare Speicherplatz, indem du den Verlauf deines CryptDrives und der Benachrichtigungen löschst. Dies hat keinen Einfluss auf den Verlauf deiner Pads. Du kannst den Verlauf der Pads in deren Eigenschaften-Dialog löschen.",
|
||||
"trimHistory_noHistory": "Kein Verlauf kann gelöscht werden",
|
||||
"settings_trimHistoryTitle": "Verlauf löschen",
|
||||
"trimHistory_currentSize": "Aktuelle Größe des Verlaufs: <b>{0}</b>",
|
||||
"trimHistory_needMigration": "Bitte <a>aktualisiere dein CryptDrive</a>, um diese Funktion zu aktivieren.",
|
||||
"trimHistory_success": "Verlauf wurde gelöscht",
|
||||
"trimHistory_error": "Fehler beim Löschen des Verlaufs",
|
||||
"trimHistory_getSizeError": "Bei der Berechnung der Größe des Verlaufs deines CryptDrives ist ein Fehler aufgetreten",
|
||||
"trimHistory_button": "Verlauf löschen",
|
||||
"historyTrim_contentsSize": "Inhalte: {0}",
|
||||
"historyTrim_historySize": "Verlauf: {0}",
|
||||
"areYouSure": "Bist du sicher?",
|
||||
"makeACopy": "Kopie erstellen",
|
||||
"copy_title": "{0} (Kopie)",
|
||||
"access_noContact": "Keine weiteren Kontakte zum Hinzufügen verfügbar",
|
||||
"access_muteRequests": "Zugriffsanfragen für dieses Pad stummschalten",
|
||||
"owner_text": "Nur Eigentümer sind berechtigt für: Hinzufügen/Entfernen von Eigentümern, Beschränkung des Zugriffs mit Zugriffslisten und Löschen des Pads.",
|
||||
"accessButton": "Zugriff",
|
||||
"access_allow": "Liste",
|
||||
"allow_label": "Zugriffsliste: {0}",
|
||||
"allow_checkbox": "Zugriffsliste aktivieren",
|
||||
"access_main": "Zugriff",
|
||||
"allow_disabled": "deaktiviert",
|
||||
"allow_enabled": "aktiviert",
|
||||
"contacts": "Kontakte",
|
||||
"restrictedError": "Du bist nicht berechtigt, auf dieses Dokument zuzugreifen",
|
||||
"allow_text": "Bei der Verwendung von Zugriffslisten können nur Eigentümer und ausgewählte Nutzer auf das Dokument zugreifen.",
|
||||
"logoutEverywhere": "Überall ausloggen",
|
||||
"teams": "Teams",
|
||||
"kanban_clearFilter": "Filter zurücksetzen",
|
||||
"kanban_noTags": "Keine Tags",
|
||||
"kanban_tags": "Nach Tags filtern",
|
||||
"kanban_conflicts": "In Bearbeitung:",
|
||||
"kanban_editBoard": "Dieses Board bearbeiten",
|
||||
"kanban_editCard": "Diese Karte bearbeiten",
|
||||
"kanban_delete": "Löschen",
|
||||
"kanban_color": "Farbe",
|
||||
"kanban_body": "Inhalt",
|
||||
"kanban_title": "Titel"
|
||||
}
|
||||
|
||||
@@ -151,7 +151,6 @@
|
||||
"okButton": "OK (enter)",
|
||||
"cancel": "Ακύρωση",
|
||||
"cancelButton": "Ακύρωση (esc)",
|
||||
"doNotAskAgain": "Να μην ρωτηθώ ξανά (Esc)",
|
||||
"historyText": "Ιστορικό",
|
||||
"historyButton": "Εμφάνιση ιστορικού του εγγράφου",
|
||||
"history_next": "Μετάβαση στην επόμενη έκδοση",
|
||||
|
||||
@@ -479,7 +479,6 @@
|
||||
"slide_invalidLess": "Estilo personalizado no válido",
|
||||
"fileShare": "Copiar link",
|
||||
"ok": "OK",
|
||||
"doNotAskAgain": "No preguntar nuevamente (Esc)",
|
||||
"show_help_button": "Mostrar ayuda",
|
||||
"hide_help_button": "Esconder ayuda",
|
||||
"help_button": "Ayuda",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"media": "Media",
|
||||
"todo": "Tehtävälista",
|
||||
"contacts": "Yhteystiedot",
|
||||
"sheet": "Taulukko (Beta)",
|
||||
"sheet": "Taulukko",
|
||||
"teams": "Teams"
|
||||
},
|
||||
"button_newpad": "Uusi Teksti-padi",
|
||||
@@ -184,7 +184,6 @@
|
||||
"okButton": "OK (Enter)",
|
||||
"cancel": "Keskeytä",
|
||||
"cancelButton": "Keskeytä (Esc)",
|
||||
"doNotAskAgain": "Älä kysy uudestaan (Esc)",
|
||||
"show_help_button": "Näytä ohje",
|
||||
"hide_help_button": "Piilota ohje",
|
||||
"help_button": "Ohje",
|
||||
@@ -619,7 +618,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_core": "Ydinkehittäjät",
|
||||
"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_footerText": "CryptPadin avulla voit nopeasti luoda kollaboratiivisia dokumentteja muistiinpanoja ja yhteistä ideointia varten.",
|
||||
"footer_applications": "Sovellukset",
|
||||
@@ -665,8 +664,6 @@
|
||||
"requestEdit_button": "Pyydä 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_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ä",
|
||||
"later": "Päätä myöhemmin",
|
||||
"requestEdit_request": "{1} haluaa muokata padia <b>{0}</b>",
|
||||
@@ -695,7 +692,6 @@
|
||||
"share_linkTeam": "Lisää tiimin CryptDriveen",
|
||||
"team_pickFriends": "Valitse tiimiin kutsuttavat kaverit",
|
||||
"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_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.",
|
||||
@@ -876,8 +872,198 @@
|
||||
"keywords": {
|
||||
"title": "Avainsanat",
|
||||
"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",
|
||||
"todo": "Todo",
|
||||
"contacts": "Contacts",
|
||||
"sheet": "Tableur (Beta)",
|
||||
"sheet": "Tableur",
|
||||
"teams": "Équipes"
|
||||
},
|
||||
"button_newpad": "Nouveau document texte",
|
||||
@@ -181,7 +181,6 @@
|
||||
"okButton": "OK (Entrée)",
|
||||
"cancel": "Annuler",
|
||||
"cancelButton": "Annuler (Échap)",
|
||||
"doNotAskAgain": "Ne plus demander (Échap)",
|
||||
"show_help_button": "Afficher l'aide",
|
||||
"hide_help_button": "Cacher l'aide",
|
||||
"help_button": "Aide",
|
||||
@@ -279,7 +278,7 @@
|
||||
"profile_description": "Description",
|
||||
"profile_fieldSaved": "Nouvelle valeur enregistrée : {0}",
|
||||
"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_addError": "Erreur lors de l'ajout de ce contact dans votre liste",
|
||||
"contacts_added": "Invitation de contact acceptée.",
|
||||
@@ -299,7 +298,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_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_friends": "Amis",
|
||||
"contacts_friends": "Contacts",
|
||||
"contacts_rooms": "Salons",
|
||||
"contacts_leaveRoom": "Quitter ce salon",
|
||||
"contacts_online": "Un autre utilisateur est en ligne dans ce salon",
|
||||
@@ -348,7 +347,7 @@
|
||||
"fm_info_root": "Créez ici autant de dossiers que vous le souhaitez pour trier vos fichiers.",
|
||||
"fm_info_unsorted": "Contient tous les pads que vous avez ouvert et qui ne sont pas triés dans \"Documents\" ou déplacés vers la \"Corbeille\".",
|
||||
"fm_info_template": "Contient tous les fichiers que vous avez sauvés en tant que modèle afin de les réutiliser lors de la création d'un nouveau pad.",
|
||||
"fm_info_recent": "Liste les derniers pads que vous avez modifiés ou ouverts.",
|
||||
"fm_info_recent": "Ces pads on été récemment ouverts ou modifiés par vous ou vos collaborateurs.",
|
||||
"fm_info_trash": "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive.",
|
||||
"fm_info_allFiles": "Contient tous les fichiers de \"Documents\", \"Fichiers non triés\" et \"Corbeille\". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.",
|
||||
"fm_info_anonymous": "Vous n'êtes pas connecté, ces pads seront donc supprimés après 3 mois d'inactivité (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">découvrez pourquoi</a>). Ils sont stockés dans votre navigateur donc nettoyer votre historique peut les faire disparaître.<br><a href=\"/register/\">Inscrivez-vous</a> ou <a href=\"/login/\">connectez-vous</a> pour les maintenir en vie.<br>",
|
||||
@@ -501,7 +500,7 @@
|
||||
"settings_pinningError": "Un problème est survenu",
|
||||
"settings_usageAmount": "Vos pads épinglés occupent {0} Mo",
|
||||
"settings_logoutEverywhereButton": "Se déconnecter",
|
||||
"settings_logoutEverywhereTitle": "Se déconnecter partout",
|
||||
"settings_logoutEverywhereTitle": "Fermer les autres sessions",
|
||||
"settings_logoutEverywhere": "Se déconnecter de force de toutes les autres sessions",
|
||||
"settings_logoutEverywhereConfirm": "Êtes-vous sûr ? Vous devrez vous reconnecter sur tous vos autres appareils.",
|
||||
"settings_driveDuplicateTitle": "Doublons des pads dont vous êtes propriétaire",
|
||||
@@ -522,8 +521,8 @@
|
||||
"settings_creationSkipFalse": "Afficher",
|
||||
"settings_templateSkip": "Passer la fenêtre de choix d'un modèle",
|
||||
"settings_templateSkipHint": "Quand vous créez un nouveau pad, et si vous possédez des modèles pour ce type de pad, une fenêtre peut apparaître pour demander si vous souhaitez importer un modèle. Ici vous pouvez choisir de ne jamais montrer cette fenêtre et donc de ne jamais utiliser de modèle.",
|
||||
"settings_ownDriveTitle": "Activer les dernières fonctionnalités du compte",
|
||||
"settings_ownDriveHint": "Pour des raisons techniques, les comptes utilisateurs les plus anciens n'ont pas accès à toutes les fonctionnalités. Une mise à niveau gratuite permet de préparer votre CryptDrive pour les nouveautés à venir sans perturber vos activités habituelles.",
|
||||
"settings_ownDriveTitle": "Mise à jour du compte",
|
||||
"settings_ownDriveHint": "Les comptes plus anciens n'ont pas accès aux dernières fonctionnalités, pour des raisons techniques. Une mise à niveau gratuite permet d'activer les fonctionnalités actuelles et de préparer votre CryptDrive pour les futures mises à jour.",
|
||||
"settings_ownDriveButton": "Mettre à niveau votre compte",
|
||||
"settings_ownDriveConfirm": "La mise à niveau peut prendre du temps. Vous devrez vous reconnecter sur tous vos appareils. Voulez-vous continuer ?",
|
||||
"settings_ownDrivePending": "Votre compte est en train d'être mis à jour. Veuillez ne pas fermer ou recharger cette page avant que le traitement soit terminé.",
|
||||
@@ -555,7 +554,7 @@
|
||||
"upload_success": "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive.",
|
||||
"upload_notEnoughSpace": "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier.",
|
||||
"upload_notEnoughSpaceBrief": "Pas assez d'espace",
|
||||
"upload_tooLarge": "Ce fichier dépasse la taille maximale autorisée.",
|
||||
"upload_tooLarge": "Ce fichier dépasse la taille maximale autorisée pour votre compte.",
|
||||
"upload_tooLargeBrief": "Fichier trop volumineux",
|
||||
"upload_choose": "Choisir un fichier",
|
||||
"upload_pending": "En attente",
|
||||
@@ -671,7 +670,7 @@
|
||||
"features_f_social": "Applications sociales",
|
||||
"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_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_note": "Les pads stockés dans votre CryptDrive ne seront jamais supprimés pour cause d'inactivité",
|
||||
"features_f_register": "S'enregistrer gratuitement",
|
||||
@@ -774,7 +773,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>."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"remove": {
|
||||
@@ -806,7 +805,7 @@
|
||||
"title": "Autres questions",
|
||||
"pay": {
|
||||
"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": {
|
||||
"q": "Quel est votre objectif ?",
|
||||
@@ -878,14 +877,14 @@
|
||||
"embed": "Intégrez des images de votre disque <span class=\"fa fa-file-image-o\"></span> ou de votre CryptDrive <span class=\"fa fa-image\"></span> et exporter le contenu en tant que PNG sur votre disque <span class=\"fa fa-download\"></span> ou votre CryptDrive <span class=\"fa fa-cloud-upload\"></span>"
|
||||
},
|
||||
"kanban": {
|
||||
"add": "Ajoutez un tableau en utilisant le bouton <span class=\"fa fa-plus\"></span> dans le coin supérieur-droit",
|
||||
"task": "Déplacez les éléments en les faisant glisser d'un tableau à l'autre",
|
||||
"color": "Modifiez les couleurs en cliquant sur les parties colorées à côté du titre de chaque tableau"
|
||||
"add": "Ajoutez des cartes et tableaux en cliquant sur les boutons <span class=\"fa fa-plus\"></span>",
|
||||
"task": "Déplacez les éléments en les faisant glisser, déposez-les dans la <span class=\"fa fa-trash\"></span> corbeille pour les effacer",
|
||||
"color": "Modifiez les titres, contenus, mots-clés, et couleurs en cliquant sur le bouton <span class=\"fa fa-pencil\"></span> à côté du titre des tableaux et des cartes"
|
||||
}
|
||||
},
|
||||
"driveReadmeTitle": "Qu'est-ce que 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_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.",
|
||||
@@ -938,7 +937,6 @@
|
||||
"creation_noTemplate": "Pas de modèle",
|
||||
"creation_newTemplate": "Nouveau modèle",
|
||||
"creation_create": "Créer",
|
||||
"creation_saveSettings": "Ne plus me demander",
|
||||
"creation_settings": "Voir davantage de préférences",
|
||||
"creation_rememberHelp": "Ouvrez votre page de Préférences pour voir ce formulaire à nouveau",
|
||||
"creation_owners": "Propriétaires",
|
||||
@@ -1006,7 +1004,6 @@
|
||||
"crowdfunding_popup_text": "<h3>Aider CryptPad</h3>Pour vous assurer que CryptPad soit activement développé, nous vous invitons à supporter le projet via la <a href=\"https://opencollective.com/cryptpad\">page OpenCollective</a>, où vous pouvez trouver notre <b>Roadmap</b> et nos <b>objectifs de financement</b>.",
|
||||
"crowdfunding_popup_yes": "Voir la page",
|
||||
"crowdfunding_popup_no": "Pas maintenant",
|
||||
"crowdfunding_popup_never": "Ne plus demander",
|
||||
"survey": "Enquête CryptPad",
|
||||
"markdown_toc": "Sommaire",
|
||||
"debug_getGraph": "Obtenir le code permettant de générer un graphe de ce document",
|
||||
@@ -1058,18 +1055,17 @@
|
||||
"friendRequest_later": "Décider plus tard",
|
||||
"friendRequest_accept": "Accepter (Entrée)",
|
||||
"friendRequest_decline": "Décliner",
|
||||
"friendRequest_declined": "<b>{0}</b> a décliné votre demande d'ami",
|
||||
"friendRequest_accepted": "<b>{0}</b> a accepté votre demande d'ami",
|
||||
"friendRequest_received": "<b>{0}</b> souhaite être votre ami",
|
||||
"friendRequest_notification": "<b>{0}</b> vous a envoyé une demande d'ami",
|
||||
"friendRequest_declined": "<b>{0}</b> a décliné votre demande de contact",
|
||||
"friendRequest_accepted": "<b>{0}</b> a accepté votre demande de contact",
|
||||
"friendRequest_received": "<b>{0}</b> souhaite être votre contact",
|
||||
"friendRequest_notification": "<b>{0}</b> vous a envoyé une demande de contact",
|
||||
"notifications_empty": "Pas de nouvelle notification",
|
||||
"notifications_title": "Vous avez des notifications non lues",
|
||||
"profile_addDescription": "Ajouter une description",
|
||||
"profile_editDescription": "Modifier votre description",
|
||||
"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_friendRequestSent": "Demande d'ami en attente...",
|
||||
"profile_friend": "{0} est votre ami(e)",
|
||||
"profile_friendRequestSent": "Demande de contact en attente...",
|
||||
"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_folderShared": "{0} a partagé un dossier avec vous : <b>{1}</b>",
|
||||
@@ -1080,7 +1076,7 @@
|
||||
"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.",
|
||||
"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",
|
||||
"supportPage": "Support",
|
||||
"admin_cat_support": "Support",
|
||||
@@ -1108,7 +1104,7 @@
|
||||
"notificationsPage": "Notifications",
|
||||
"openNotificationsApp": "Ouvrir le panneau de notifications",
|
||||
"notifications_cat_all": "Toutes",
|
||||
"notifications_cat_friends": "Demandes d'ami",
|
||||
"notifications_cat_friends": "Demandes de contact",
|
||||
"notifications_cat_pads": "Partagé avec moi",
|
||||
"notifications_cat_archived": "Historique",
|
||||
"notifications_dismissAll": "Tout cacher",
|
||||
@@ -1122,8 +1118,6 @@
|
||||
"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_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",
|
||||
"later": "Décider plus tard",
|
||||
"requestEdit_request": "{1} souhaite éditer le pad <b>{0}</b>",
|
||||
@@ -1148,10 +1142,10 @@
|
||||
"register_emailWarning1": "Vous pouvez continuer, mais ces données ne sont pas nécessaires et ne seront pas envoyées à notre serveur.",
|
||||
"register_emailWarning2": "Vous ne pourrez pas réinitialiser votre mot de passe en utilisant votre adresse email comme sur beaucoup d'autres services.",
|
||||
"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_removePendingText": "Annuler une offre en attente",
|
||||
"owner_addText": "Proposer à un ami d'être co-propriétaire de ce document",
|
||||
"owner_unknownUser": "Utilisateur inconnu",
|
||||
"owner_removeText": "Propriétaires",
|
||||
"owner_removePendingText": "En attente",
|
||||
"owner_addText": "Proposer à un contact d'être co-propriétaire de ce document",
|
||||
"owner_unknownUser": "inconnu",
|
||||
"owner_removeButton": "Supprimer les propriétaires sélectionnés",
|
||||
"owner_removePendingButton": "Annuler les offres sélectionnées",
|
||||
"owner_addButton": "Proposer d'être propriétaire",
|
||||
@@ -1167,9 +1161,8 @@
|
||||
"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": "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_noFriend": "Vous n'avez pas encore ajouté d'ami sur CryptPad.",
|
||||
"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_invitedToTeam": "{0} vous à inviter à rejoindre l'équipe : <b>{1}</b>",
|
||||
@@ -1191,7 +1184,7 @@
|
||||
"team_rosterPromote": "Promouvoir",
|
||||
"team_rosterDemote": "Rétrograder",
|
||||
"team_rosterKick": "Expulser de l'équipe",
|
||||
"team_inviteButton": "Inviter des amis",
|
||||
"team_inviteButton": "Inviter des contacts",
|
||||
"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_owner": "Propriétaires",
|
||||
@@ -1294,5 +1287,54 @@
|
||||
"oo_exportInProgress": "Exportation en cours",
|
||||
"oo_sheetMigration_loading": "Mise à jour de la feuille de calcul",
|
||||
"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",
|
||||
"settings_safeLinksHint": "CryptPad inclut dans ses liens les clés permettant de déchiffrer vos pads. Toute personne ayant accès à votre historique de navigation peut potentiellement lire vos données. Cela inclut les extensions de navigateur intrusives et les navigateurs qui synchronisent votre historique entre les appareils. L'activation des \"liens sécurisés\" empêche les clés d'entrer dans votre historique de navigation ou d'être affichées dans votre barre d'adresse quand cela est possible. Nous vous recommandons vivement d'activer cette fonction et d'utiliser le menu {0} Partager.",
|
||||
"profile_login": "Vous devez vous connecter pour ajouter cet utilisateur à vos contacts",
|
||||
"dontShowAgain": "Ne plus demander",
|
||||
"safeLinks_error": "Le lien utilisé ne permet pas d'ouvrir ce document",
|
||||
"settings_safeLinksCheckbox": "Activer les liens sécurisés",
|
||||
"settings_safeLinksTitle": "Liens Sécurisés",
|
||||
"settings_cat_security": "Confidentialité",
|
||||
"settings_trimHistoryHint": "Économisez de l'espace de stockage en supprimant l'historique de votre disque et de vos notifications. Cela n'affectera pas l'historique de vos documents. Vous pouvez supprimer l'historique des pads dans leur dialogue de propriétés.",
|
||||
"settings_trimHistoryTitle": "Effacer l'Historique",
|
||||
"trimHistory_noHistory": "Il n'y a pas d'historique à supprimer",
|
||||
"trimHistory_currentSize": "Taille de l'historique : <b>{0}</b>",
|
||||
"trimHistory_needMigration": "Merci de <a>mettre votre CryptDrive à jour</a> pour activer cette fonctionalité.",
|
||||
"trimHistory_success": "L'historique a été effacé",
|
||||
"trimHistory_error": "Erreur lors de la suppression de l'historique",
|
||||
"trimHistory_getSizeError": "Erreur lors du calcul de la taille de l'historique de votre drive",
|
||||
"trimHistory_button": "Effacer l'historique",
|
||||
"historyTrim_contentsSize": "Contenu : {0}",
|
||||
"historyTrim_historySize": "Historique : {0}",
|
||||
"areYouSure": "Êtes-vous sûr ?",
|
||||
"copy_title": "{0} (copie)",
|
||||
"makeACopy": "Créer une copie",
|
||||
"owner_text": "Le(s) propriétaire(s) d'un pad sont les seuls utilisateurs autorisés à : ajouter/supprimer des propriétaires, restreindre l'accès au bloc-notes avec une liste d'accès, ou à supprimer le pad.",
|
||||
"access_muteRequests": "Masquer les requêtes d'accès pour ce pad",
|
||||
"allow_label": "Liste d'accès : {0}",
|
||||
"allow_disabled": "désactivée",
|
||||
"allow_enabled": "activée",
|
||||
"allow_checkbox": "Activer la liste d'accès",
|
||||
"access_noContact": "Il n'y a plus de contacts à ajouter",
|
||||
"contacts": "Contacts",
|
||||
"restrictedError": "Vous n'êtes pas autorisé à accéder à ce document",
|
||||
"accessButton": "Accès",
|
||||
"access_allow": "Liste",
|
||||
"access_main": "Accès",
|
||||
"logoutEverywhere": "Se déconnecter partout",
|
||||
"allow_text": "L'utilisation d'une liste d'accès signifie que seuls les utilisateurs et propriétaires sélectionnés pourront accéder à ce document.",
|
||||
"teams": "Équipes",
|
||||
"kanban_editBoard": "Modifier ce tableau",
|
||||
"kanban_editCard": "Modifier cette carte",
|
||||
"kanban_clearFilter": "Enlever le filtre",
|
||||
"kanban_conflicts": "En cours d'édition :",
|
||||
"kanban_noTags": "Pas de mots-clés",
|
||||
"kanban_tags": "Filtrer par mot-clé",
|
||||
"kanban_delete": "Supprimer",
|
||||
"kanban_color": "Couleur",
|
||||
"kanban_body": "Contenu",
|
||||
"kanban_title": "Titre"
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
"pad": "Testo",
|
||||
"code": "Code",
|
||||
"code": "Codice",
|
||||
"poll": "Sondaggio",
|
||||
"kanban": "Kanban",
|
||||
"slide": "Presentazione",
|
||||
@@ -10,13 +10,13 @@
|
||||
"whiteboard": "Lavagna",
|
||||
"file": "File",
|
||||
"media": "Media",
|
||||
"todo": "Todo",
|
||||
"todo": "Promemoria",
|
||||
"contacts": "Contatti",
|
||||
"sheet": "Fogli (Beta)",
|
||||
"sheet": "Fogli",
|
||||
"teams": "Team"
|
||||
},
|
||||
"button_newpad": "Nuovo pad di Testo",
|
||||
"button_newcode": "Nuovo pad di Code",
|
||||
"button_newcode": "Nuovo pad di Codice",
|
||||
"button_newpoll": "Nuovo Sondaggio",
|
||||
"button_newslide": "Nuova Presentazione",
|
||||
"button_newwhiteboard": "Nuova Lavagna",
|
||||
@@ -34,7 +34,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"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...",
|
||||
@@ -179,9 +179,8 @@
|
||||
"notifyLeft": "{0} ha abbandonato la sessione collaborativa",
|
||||
"ok": "OK",
|
||||
"okButton": "OK (Enter)",
|
||||
"cancel": "Cancella",
|
||||
"cancel": "Annulla",
|
||||
"cancelButton": "Cancella (Esc)",
|
||||
"doNotAskAgain": "Non chiedere più (Esc)",
|
||||
"show_help_button": "Mostra l'aiuto",
|
||||
"hide_help_button": "Nascondi l'aiuto",
|
||||
"help_button": "Aiuto",
|
||||
@@ -299,7 +298,7 @@
|
||||
"contacts_confirmRemoveHistory": "Sei sicuro di voler rimuovere permanentemente la cronologia della chat? I dati non possono essere recuperati",
|
||||
"contacts_removeHistoryServerError": "C'è stato un errore nella cancellazione della cronologia della chat. Prova di nuovo più tardi",
|
||||
"contacts_fetchHistory": "Recupera messaggi precedenti",
|
||||
"contacts_friends": "Amici",
|
||||
"contacts_friends": "Contatti",
|
||||
"contacts_rooms": "Stanze",
|
||||
"contacts_leaveRoom": "Esci da questa stanza",
|
||||
"contacts_online": "Un altro utente di questa stanza è online",
|
||||
@@ -424,7 +423,7 @@
|
||||
"register_whyRegister": "Perché registrarsi?",
|
||||
"register_header": "Benvenuto su CryptPad",
|
||||
"fm_alert_anonymous": "",
|
||||
"register_writtenPassword": "Ho annotato il mio username e la mia password, procedi",
|
||||
"register_writtenPassword": "Ho annotato il mio nome utente e la mia password, procedi",
|
||||
"register_cancel": "Torna indietro",
|
||||
"register_warning": "Zero Knowledge significa che non possiamo recuperare i tuoi dati se perdi la tua password.",
|
||||
"register_alreadyRegistered": "Questo utente esiste già, vuoi effettuare il log in?",
|
||||
@@ -447,22 +446,22 @@
|
||||
"settings_exportTitle": "Esporta il tuo CryptDrive",
|
||||
"settings_exportDescription": "Per favore attendi mentre scarichiamo e decriptiamo i tuoi documenti. Potrebbe richiedere qualche minuto. Chiudere la finestra interromperà il processo.",
|
||||
"settings_exportFailed": "Se il pad richiede più di un minuto per essere scaricato, non sarà incluso nell'export. Un link a qualsiasi pad non esportato sarà mostrato.",
|
||||
"settings_exportWarning": "",
|
||||
"settings_exportCancel": "",
|
||||
"settings_export_reading": "",
|
||||
"settings_export_download": "",
|
||||
"settings_export_compressing": "",
|
||||
"settings_export_done": "",
|
||||
"settings_exportError": "",
|
||||
"settings_exportErrorDescription": "",
|
||||
"settings_exportErrorEmpty": "",
|
||||
"settings_exportErrorMissing": "",
|
||||
"settings_exportErrorOther": "",
|
||||
"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": "Sei sicuro di voler cancellare l'export? Dovrai iniziare da capo la prossima volta.",
|
||||
"settings_export_reading": "Lettura del tuo CryptDrive in corso...",
|
||||
"settings_export_download": "Scaricamento e decriptazione dei tuoi documenti in corso...",
|
||||
"settings_export_compressing": "Compressione dei dati in corso...",
|
||||
"settings_export_done": "Il tuo download è pronto!",
|
||||
"settings_exportError": "Visualizza errori",
|
||||
"settings_exportErrorDescription": "Non siamo riusciti ad aggiungere i seguenti documenti all'export:",
|
||||
"settings_exportErrorEmpty": "Questo documento non può essere esportato (contenuto vuoto o invalido).",
|
||||
"settings_exportErrorMissing": "Questo documento non è stato trovato nei nostri server (scaduto o rimosso dal suo proprietario)",
|
||||
"settings_exportErrorOther": "È accaduto un errore durante l'esportazione di questo documento: {0}",
|
||||
"settings_resetNewTitle": "Pulisci CryptDrive",
|
||||
"settings_resetButton": "",
|
||||
"settings_resetButton": "Rimuovi",
|
||||
"settings_reset": "Rimuovi tutti i file e le cartelle dal tuo CryptDrive",
|
||||
"settings_resetPrompt": "",
|
||||
"settings_resetDone": "",
|
||||
"settings_resetDone": "Il tuo drive è vuoto adesso!",
|
||||
"settings_resetError": "",
|
||||
"settings_resetTipsAction": "",
|
||||
"settings_resetTips": "",
|
||||
@@ -513,5 +512,31 @@
|
||||
},
|
||||
"readme_cat3_l1": "Con l'editor di codice di CryptPad, puoi collaborare su linguaggi di programmazione come Javascript e linguaggi di markup come HTML o Markdown",
|
||||
"settings_codeSpellcheckLabel": "Abilita la revisione ortografica nell'editor di codice",
|
||||
"team_inviteLinkError": "Si è verificato un errore durante la creazione del link."
|
||||
"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.",
|
||||
"faq": {
|
||||
"usability": {
|
||||
"devices": {
|
||||
"a": "nome utente"
|
||||
},
|
||||
"forget": {
|
||||
"a": "nome utente"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"crypto": {
|
||||
"a": "nome utente"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"register": {
|
||||
"a": "nome utente"
|
||||
}
|
||||
}
|
||||
},
|
||||
"whatis_zeroknowledge_p2": "Quando ti registri e accedi, il tuo nome utente e la tua password vengono computati in una chiave segreta utilizzando la <a href=\"https://en.wikipedia.org/wiki/Scrypt\">funzione di derivazione scrypt</a>. Ne questa chiave, ne il tuo nome utente o la tua password vengono inviati al server. Infatti sono usati soltanto dal lato client per decriptare il contenuto del tuo CryptDrive, che contiene le chiavi per tutti i pad a cui hai accesso.",
|
||||
"faq_title": "Domande frequenti"
|
||||
}
|
||||
|
||||
@@ -184,7 +184,6 @@
|
||||
"okButton": "OK (enter)",
|
||||
"cancel": "Cancel",
|
||||
"cancelButton": "Cancel (esc)",
|
||||
"doNotAskAgain": "Don't ask me again (Esc)",
|
||||
"show_help_button": "Show help",
|
||||
"hide_help_button": "Hide help",
|
||||
"help_button": "Help",
|
||||
@@ -282,7 +281,7 @@
|
||||
"profile_description": "Description",
|
||||
"profile_fieldSaved": "New value saved: {0}",
|
||||
"profile_viewMyProfile": "View my profile",
|
||||
"userlist_addAsFriendTitle": "Send \"{0}\" a friend request",
|
||||
"userlist_addAsFriendTitle": "Send \"{0}\" a contact request",
|
||||
"contacts_title": "Contacts",
|
||||
"contacts_addError": "Error while adding that contact to the list",
|
||||
"contacts_added": "Contact invite accepted.",
|
||||
@@ -302,7 +301,7 @@
|
||||
"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_fetchHistory": "Retrieve older history",
|
||||
"contacts_friends": "Friends",
|
||||
"contacts_friends": "Contacts",
|
||||
"contacts_rooms": "Rooms",
|
||||
"contacts_leaveRoom": "Leave this room",
|
||||
"contacts_online": "Another user from this room is online",
|
||||
@@ -355,7 +354,7 @@
|
||||
"fm_info_root": "Create as many nested folders here as you want to sort your files.",
|
||||
"fm_info_unsorted": "Contains all the files you've visited that are not yet sorted in \"Documents\" or moved to the \"Trash\".",
|
||||
"fm_info_template": "Contains all the pads stored as templates and that you can re-use when you create a new pad.",
|
||||
"fm_info_recent": "List the recently modified or opened pads.",
|
||||
"fm_info_recent": "These pads have recently been opened or modified by you or people you collaborate with.",
|
||||
"fm_info_trash": "Empty your trash to free space in your CryptDrive.",
|
||||
"fm_info_allFiles": "Contains all the files from \"Documents\", \"Unsorted\" and \"Trash\". You can't move or remove files from here.",
|
||||
"fm_info_anonymous": "You are not logged in so your pads will expire after 3 months (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">find out more</a>). They are stored in your browser so clearing history may make them disappear.<br><a href=\"/register/\">Sign up</a> or <a href=\"/login/\">Log in</a> to keep them alive.<br>",
|
||||
@@ -514,7 +513,7 @@
|
||||
"settings_pinningError": "Something went wrong",
|
||||
"settings_usageAmount": "Your pinned pads occupy {0}MB",
|
||||
"settings_logoutEverywhereButton": "Log out",
|
||||
"settings_logoutEverywhereTitle": "Log out everywhere",
|
||||
"settings_logoutEverywhereTitle": "Close remote sessions",
|
||||
"settings_logoutEverywhere": "Force log out of all other web sessions",
|
||||
"settings_logoutEverywhereConfirm": "Are you sure? You will need to log in with all your devices.",
|
||||
"settings_driveDuplicateTitle": "Duplicated owned pads",
|
||||
@@ -535,8 +534,8 @@
|
||||
"settings_creationSkipFalse": "Display",
|
||||
"settings_templateSkip": "Skip the template selection modal",
|
||||
"settings_templateSkipHint": "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template.",
|
||||
"settings_ownDriveTitle": "Enable latest account features",
|
||||
"settings_ownDriveHint": "For technical reasons, older accounts do not have access to all of our latest features. A free upgrade to a new account will prepare your CryptDrive for upcoming features without disrupting your usual activities.",
|
||||
"settings_ownDriveTitle": "Update Account",
|
||||
"settings_ownDriveHint": "Older accounts do not have access to the latest features, due to technical reasons. A free update will enable current features, and prepare your CryptDrive for future updates.",
|
||||
"settings_ownDriveButton": "Upgrade your account",
|
||||
"settings_ownDriveConfirm": "Upgrading your account may take some time. You will need to log back in on all your devices. Are you sure?",
|
||||
"settings_ownDrivePending": "Your account is being upgraded. Please do not close or reload this page until the process has completed.",
|
||||
@@ -572,7 +571,7 @@
|
||||
"upload_success": "Your file ({0}) has been successfully uploaded and added to your drive.",
|
||||
"upload_notEnoughSpace": "There is not enough space for this file in your CryptDrive.",
|
||||
"upload_notEnoughSpaceBrief": "Not enough space",
|
||||
"upload_tooLarge": "This file exceeds the maximum upload size.",
|
||||
"upload_tooLarge": "This file exceeds the maximum upload size allowed for your account.",
|
||||
"upload_tooLargeBrief": "File too large",
|
||||
"upload_choose": "Choose a file",
|
||||
"upload_pending": "Pending",
|
||||
@@ -689,7 +688,7 @@
|
||||
"features_f_social": "Social applications",
|
||||
"features_f_social_note": "Create a profile, use an avatar, chat with contacts",
|
||||
"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_note": "Pads stored in your CryptDrive are never deleted for inactivity",
|
||||
"features_f_register": "Register for free",
|
||||
@@ -792,7 +791,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>."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"remove": {
|
||||
@@ -824,7 +823,7 @@
|
||||
"title": "Other questions",
|
||||
"pay": {
|
||||
"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": {
|
||||
"q": "What is your goal?",
|
||||
@@ -885,7 +884,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"
|
||||
},
|
||||
"poll": {
|
||||
"decisions": "Make decisions in private among trusted friends",
|
||||
"decisions": "Make decisions in private among trusted contacts",
|
||||
"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>)",
|
||||
"submit": "Click <strong>submit</strong> to make your choices visible to others"
|
||||
@@ -896,14 +895,14 @@
|
||||
"embed": "Embed images from your disk <span class=\"fa fa-file-image-o\"></span> or your CryptDrive <span class=\"fa fa-image\"></span> and export them as PNG to your disk <span class=\"fa fa-download\"></span> or your CryptDrive <span class=\"fa fa-cloud-upload\"></span>"
|
||||
},
|
||||
"kanban": {
|
||||
"add": "Add new boards using the <span class=\"fa fa-plus\"></span> button in the top-right corner",
|
||||
"task": "Move items by dragging and dropping them from one board to another",
|
||||
"color": "Change the colors by clicking on the colored part next to the board titles"
|
||||
"add": "Add new cards and boards by clicking the <span class=\"fa fa-plus\"></span> buttons",
|
||||
"task": "Move items by dragging and dropping, drag to the <span class=\"fa fa-trash\"></span> trash to delete",
|
||||
"color": "Edit titles, content, tags, and colors by clicking on the <span class=\"fa fa-pencil\"></span> button next to card and board titles"
|
||||
}
|
||||
},
|
||||
"driveReadmeTitle": "What is 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_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.",
|
||||
@@ -936,7 +935,7 @@
|
||||
"feedback_about": "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions",
|
||||
"feedback_privacy": "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.",
|
||||
"feedback_optout": "If you would like to opt out, visit <a href='/settings/'>your user settings page</a>, where you'll find a checkbox to enable or disable user feedback",
|
||||
"creation_404": "This pad not longer exists. Use the following form to create a new pad.",
|
||||
"creation_404": "This pad no longer exists. Use the following form to create a new pad.",
|
||||
"creation_ownedTitle": "Type of pad",
|
||||
"creation_owned": "Owned pad",
|
||||
"creation_ownedTrue": "Owned pad",
|
||||
@@ -956,7 +955,6 @@
|
||||
"creation_noTemplate": "No template",
|
||||
"creation_newTemplate": "New template",
|
||||
"creation_create": "Create",
|
||||
"creation_saveSettings": "Don't show this again",
|
||||
"creation_settings": "View more settings",
|
||||
"creation_rememberHelp": "Visit your Settings page to reset this preference",
|
||||
"creation_owners": "Owners",
|
||||
@@ -1028,7 +1026,6 @@
|
||||
"crowdfunding_popup_text": "<h3>We need your help!</h3>To ensure that CryptPad is actively developed, consider supporting the project via the <a href=\"https://opencollective.com/cryptpad\">OpenCollective page</a>, where you can see our <b>Roadmap</b> and <b>Funding goals</b>.",
|
||||
"crowdfunding_popup_yes": "Go to OpenCollective",
|
||||
"crowdfunding_popup_no": "Not now",
|
||||
"crowdfunding_popup_never": "Don't ask me again",
|
||||
"survey": "CryptPad survey",
|
||||
"markdown_toc": "Contents",
|
||||
"fm_expirablePad": "Expires: {0}",
|
||||
@@ -1075,18 +1072,19 @@
|
||||
"friendRequest_later": "Decide later",
|
||||
"friendRequest_accept": "Accept (Enter)",
|
||||
"friendRequest_decline": "Decline",
|
||||
"friendRequest_declined": "<b>{0}</b> declined your friend request",
|
||||
"friendRequest_accepted": "<b>{0}</b> accepted your friend request",
|
||||
"friendRequest_received": "<b>{0}</b> would like to be your friend",
|
||||
"friendRequest_notification": "<b>{0}</b> sent you a friend request",
|
||||
"friendRequest_declined": "<b>{0}</b> declined your contact request",
|
||||
"friendRequest_accepted": "<b>{0}</b> accepted your contact request",
|
||||
"friendRequest_received": "<b>{0}</b> would like to be your contact",
|
||||
"friendRequest_notification": "<b>{0}</b> sent you a contact request",
|
||||
"notifications_empty": "No notifications available",
|
||||
"notifications_title": "You have unread notifications",
|
||||
"profile_addDescription": "Add a description",
|
||||
"profile_editDescription": "Edit your description",
|
||||
"profile_addLink": "Add a link to your website",
|
||||
"profile_info": "Other users can find your profile through your avatar in document user lists.",
|
||||
"profile_friendRequestSent": "Friend request pending...",
|
||||
"profile_friend": "{0} is your friend",
|
||||
"profile_friendRequestSent": "Contact request pending...",
|
||||
"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_fileShared": "{0} has shared a file with you: <b>{1}</b>",
|
||||
"notification_folderShared": "{0} has shared a folder with you: <b>{1}</b>",
|
||||
@@ -1097,7 +1095,7 @@
|
||||
"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.",
|
||||
"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",
|
||||
"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.",
|
||||
@@ -1130,7 +1128,7 @@
|
||||
"notificationsPage": "Notifications",
|
||||
"openNotificationsApp": "Open notifications panel",
|
||||
"notifications_cat_all": "All",
|
||||
"notifications_cat_friends": "Friend requests",
|
||||
"notifications_cat_friends": "Contact requests",
|
||||
"notifications_cat_pads": "Shared with me",
|
||||
"notifications_cat_archived": "History",
|
||||
"notifications_dismissAll": "Dismiss all",
|
||||
@@ -1138,8 +1136,6 @@
|
||||
"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_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",
|
||||
"later": "Decide later",
|
||||
"requestEdit_request": "{1} wants to edit the pad <b>{0}</b>",
|
||||
@@ -1151,10 +1147,10 @@
|
||||
"features_noData": "No personal information required",
|
||||
"features_pricing": "Between {0} and {2}€ per month",
|
||||
"features_emailRequired": "Email address required",
|
||||
"owner_removeText": "Remove an existing owner",
|
||||
"owner_removePendingText": "Cancel a pending offer",
|
||||
"owner_addText": "Offer co-ownership to a friend",
|
||||
"owner_unknownUser": "Unknown user",
|
||||
"owner_removeText": "Owners",
|
||||
"owner_removePendingText": "Pending",
|
||||
"owner_addText": "Offer co-ownership to a contact",
|
||||
"owner_unknownUser": "unknown",
|
||||
"owner_removeButton": "Remove selected owners",
|
||||
"owner_removePendingButton": "Cancel selected offers",
|
||||
"owner_addButton": "Offer ownership",
|
||||
@@ -1169,9 +1165,8 @@
|
||||
"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": "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_noFriend": "You haven't connected with any friends on CryptPad yet.",
|
||||
"team_pcsSelectLabel": "Store in",
|
||||
"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>",
|
||||
@@ -1193,7 +1188,7 @@
|
||||
"team_rosterPromote": "Promote",
|
||||
"team_rosterDemote": "Demote",
|
||||
"team_rosterKick": "Kick from the team",
|
||||
"team_inviteButton": "Invite friends",
|
||||
"team_inviteButton": "Invite contacts",
|
||||
"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_owner": "Owners",
|
||||
@@ -1294,5 +1289,52 @@
|
||||
"oo_exportInProgress": "Export in progress",
|
||||
"oo_sheetMigration_loading": "Upgrading your spreadsheet to the latest version",
|
||||
"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",
|
||||
"settings_cat_security": "Confidentiality",
|
||||
"settings_safeLinksTitle": "Safe Links",
|
||||
"settings_safeLinksCheckbox": "Enable safe links",
|
||||
"safeLinks_error": "This link does not give you access to the document",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"profile_login": "You need to log in to add this user to your contacts",
|
||||
"settings_safeLinksHint": "CryptPad includes the keys to decrypt your pads in their links. Anyone with access to your browsing history can potentially read your data. This includes intrusive browser extensions and browsers that sync your history across devices. Enabling \"safe links\" prevents the keys from entering your browsing history or being displayed in your address bar whenever possible. We strongly recommend that you enable this feature and use the {0} Share menu.",
|
||||
"areYouSure": "Are you sure?",
|
||||
"historyTrim_historySize": "History: {0}",
|
||||
"historyTrim_contentsSize": "Contents: {0}",
|
||||
"trimHistory_button": "Delete History",
|
||||
"trimHistory_getSizeError": "Error while calculating the size of your drive's history",
|
||||
"trimHistory_error": "Error while deleting history",
|
||||
"trimHistory_success": "History has been deleted",
|
||||
"trimHistory_needMigration": "Please <a>update your CryptDrive</a> to enable this feature.",
|
||||
"trimHistory_currentSize": "Current history size: <b>{0}</b>",
|
||||
"trimHistory_noHistory": "No history can be deleted",
|
||||
"settings_trimHistoryTitle": "Delete History",
|
||||
"settings_trimHistoryHint": "Save storage space by deleting the history of your drive and notifications. This will not affect the history of your pads. You can delete the history of pads in their properties dialog.",
|
||||
"makeACopy": "Make a copy",
|
||||
"copy_title": "{0} (copy)",
|
||||
"access_main": "Access",
|
||||
"access_allow": "List",
|
||||
"accessButton": "Access",
|
||||
"restrictedError": "You are not authorised to access this document",
|
||||
"contacts": "Contacts",
|
||||
"access_noContact": "No other contact to add",
|
||||
"allow_checkbox": "Enable access list",
|
||||
"allow_enabled": "enabled",
|
||||
"allow_disabled": "disabled",
|
||||
"allow_label": "Access list: {0}",
|
||||
"access_muteRequests": "Mute access requests for this pad",
|
||||
"owner_text": "The owner(s) of a pad are the only users authorised to: add/remove owners, restrict access to the pad with an access list, or delete the pad.",
|
||||
"logoutEverywhere": "Log out everywhere",
|
||||
"allow_text": "Using an access list means that only selected users and owners will be able to access this document.",
|
||||
"teams": "Teams",
|
||||
"kanban_title": "Title",
|
||||
"kanban_body": "Content",
|
||||
"kanban_color": "Color",
|
||||
"kanban_delete": "Delete",
|
||||
"kanban_tags": "Filter by tag",
|
||||
"kanban_noTags": "No tags",
|
||||
"kanban_conflicts": "Currently editing:",
|
||||
"kanban_clearFilter": "Clear filter",
|
||||
"kanban_editCard": "Edit this card",
|
||||
"kanban_editBoard": "Edit this board"
|
||||
}
|
||||
|
||||
@@ -193,7 +193,6 @@
|
||||
"crowdfunding_button": "Støtt CryptPad",
|
||||
"crowdfunding_popup_yes": "Gå til OpenCollective",
|
||||
"crowdfunding_popup_no": "Ikke nå",
|
||||
"crowdfunding_popup_never": "Ikke spør igjen takk",
|
||||
"markdown_toc": "Innhold",
|
||||
"fm_expirablePad": "Denne paden vill utgå på dato den {0}",
|
||||
"admin_authError": "Kun admin-tilgang",
|
||||
|
||||
@@ -409,7 +409,6 @@
|
||||
"fileEmbedScript": "",
|
||||
"fileEmbedTag": "",
|
||||
"ok": "",
|
||||
"doNotAskAgain": "",
|
||||
"show_help_button": "",
|
||||
"hide_help_button": "",
|
||||
"help_button": "",
|
||||
|
||||
@@ -395,7 +395,6 @@
|
||||
"fileEmbedTitle": "Include fișierul într-o pagină externă",
|
||||
"fileEmbedTag": "După care plasează această etichetă Media oriunde pe pagina unde vrei sa o plasezi",
|
||||
"ok": "Ok",
|
||||
"doNotAskAgain": "Nu mă întreba din nou (Esc)",
|
||||
"show_help_button": "Arată ajutorul",
|
||||
"hide_help_button": "Maschează ajutorul",
|
||||
"help_button": "Ajutor",
|
||||
|
||||
@@ -175,7 +175,6 @@
|
||||
"okButton": "OK (Enter)",
|
||||
"cancel": "Отмена",
|
||||
"cancelButton": "Отмена (Esc)",
|
||||
"doNotAskAgain": "Не спрашивать снова (Esc)",
|
||||
"show_help_button": "Показать справку",
|
||||
"hide_help_button": "Скрыть справку",
|
||||
"help_button": "Справка",
|
||||
@@ -299,7 +298,6 @@
|
||||
"fm_removeSeveralPermanentlyDialog": "Вы уверены, что хотите навсегда удалить {0} элементов из вашего Хранилища?",
|
||||
"crowdfunding_button": "Поддержите CryptPad",
|
||||
"crowdfunding_popup_no": "Не сейчас",
|
||||
"crowdfunding_popup_never": "Не спрашивать меня снова",
|
||||
"markdown_toc": "Содержимое",
|
||||
"fm_expirablePad": "Этот блокнот истечет {0}",
|
||||
"fileEmbedTitle": "Встроить файл во внешнюю страницу",
|
||||
|
||||
Reference in New Issue
Block a user