diff --git a/customize.dist/pages.js b/customize.dist/pages.js
index bdae16b01..b5d277e81 100644
--- a/customize.dist/pages.js
+++ b/customize.dist/pages.js
@@ -103,7 +103,7 @@ define([
])*/
])
]),
- h('div.cp-version-footer', "CryptPad v2.21.0 (Vervet)")
+ h('div.cp-version-footer', "CryptPad v2.22.0 (Wolf)")
]);
};
diff --git a/package.json b/package.json
index 7a6dd8b18..bda497b7d 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
- "version": "2.21.0",
+ "version": "2.22.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 79863a527..9c1d2690c 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -452,7 +452,7 @@ define([
}
common.getAttribute(['general', 'share'], function (err, val) {
val = val || {};
- if (val.edit === false) {
+ if (val.edit === false || !hashes.editHash) {
$(link).find('#cp-share-editable-false').prop('checked', true);
$(link).find('#cp-share-editable-true').prop('checked', false);
} else {
@@ -719,7 +719,8 @@ define([
}
}
sframeChan.query('Q_SAVE_AS_TEMPLATE', {
- toSave: toSave
+ toSave: toSave,
+ title: title
}, function () {
UI.alert(Messages.templateSaved);
Feedback.send('TEMPLATE_CREATED');
diff --git a/www/common/cryptget.js b/www/common/cryptget.js
index 5279a17c9..d08b4b297 100644
--- a/www/common/cryptget.js
+++ b/www/common/cryptget.js
@@ -28,9 +28,9 @@ define([
}
};
- var makeConfig = function (hash, password) {
+ var makeConfig = function (hash, opt) {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
- var secret = Hash.getSecrets('pad', hash, password);
+ var secret = Hash.getSecrets('pad', hash, opt.password);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: NetConfig.getWebsocketURL(),
@@ -38,6 +38,7 @@ define([
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
logLevel: 0,
+ initialState: opt.initialState
};
return config;
};
@@ -57,7 +58,7 @@ define([
}
opt = opt || {};
- var config = makeConfig(hash, opt.password);
+ var config = makeConfig(hash, opt);
var Session = { cb: cb, hasNetwork: Boolean(opt.network) };
config.onReady = function (info) {
@@ -82,7 +83,7 @@ define([
}
opt = opt || {};
- var config = makeConfig(hash, opt.password);
+ var config = makeConfig(hash, opt);
var Session = { cb: cb, };
config.onReady = function (info) {
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index 9f1d3f544..324e6cab9 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -477,6 +477,9 @@ define([
// PPP: password for the new template?
var hash = Hash.createRandomHash(p.type);
var href = '/' + p.type + '/#' + hash;
+
+ var optsPut = {};
+ if (p.type === 'poll') { optsPut.initialState = '{}'; }
// PPP: add password as cryptput option
Cryptput(hash, data.toSave, function (e) {
if (e) { throw new Error(e); }
@@ -488,7 +491,7 @@ define([
if (obj && obj.error) { return void cb(obj.error); }
cb();
});
- });
+ }, optsPut);
};
common.isTemplate = function (href, cb) {
@@ -512,6 +515,10 @@ define([
optsPut = optsPut || {};
var optsGet = {};
+
+ if (parsed.type === 'poll') { optsGet.initialState = '{}'; }
+ if (parsed2.type === 'poll') { optsPut.initialState = '{}'; }
+
Nthen(function (waitFor) {
if (parsed.hashData && parsed.hashData.password) {
common.getPadAttribute('password', waitFor(function (err, password) {
@@ -656,6 +663,12 @@ define([
};
cursor.onEvent = Util.mkEvent();
+ // Mailbox
+ var mailbox = common.mailbox = {};
+ mailbox.execCommand = function (data, cb) {
+ postMessage("MAILBOX_COMMAND", data, cb);
+ };
+ mailbox.onEvent = Util.mkEvent();
// Pad RPC
var pad = common.padRpc = {};
@@ -1096,6 +1109,8 @@ define([
CHAT_EVENT: common.messenger.onEvent.fire,
// Cursor
CURSOR_EVENT: common.cursor.onEvent.fire,
+ // Mailbox
+ MAILBOX_EVENT: common.mailbox.onEvent.fire,
// Pad
PAD_READY: common.padRpc.onReadyEvent.fire,
PAD_MESSAGE: common.padRpc.onMessageEvent.fire,
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index efa05d85d..e174ecad3 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -12,18 +12,19 @@ define([
'/common/common-messenger.js',
'/common/outer/cursor.js',
'/common/outer/onlyoffice.js',
- '/common/outer/chainpad-netflux-worker.js',
+ '/common/outer/mailbox.js',
'/common/outer/network-config.js',
'/customize/application_config.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad/chainpad.dist.js',
+ '/bower_components/chainpad-netflux/chainpad-netflux.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js',
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
- Cursor, OnlyOffice, CpNfWorker, NetConfig, AppConfig,
- Crypto, ChainPad, Listmap, nThen, Saferphore) {
+ Cursor, OnlyOffice, Mailbox, NetConfig, AppConfig,
+ Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) {
var create = function () {
var Store = window.Cryptpad_Store = {};
@@ -478,10 +479,11 @@ define([
Store.addPad = function (clientId, data, cb) {
if (!data.href && !data.roHref) { return void cb({error:'NO_HREF'}); }
+ var secret;
if (!data.roHref) {
var parsed = Hash.parsePadUrl(data.href);
if (parsed.hashData.type === "pad") {
- var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
+ secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
}
}
@@ -489,7 +491,7 @@ define([
if (data.owners) { pad.owners = data.owners; }
if (data.expire) { pad.expire = data.expire; }
if (data.password) { pad.password = data.password; }
- if (data.channel) { pad.channel = data.channel; }
+ if (data.channel || secret) { pad.channel = data.channel || secret.channel; }
store.manager.addPad(data.path, pad, function (e) {
if (e) { return void cb({error: e}); }
sendDriveEvent('DRIVE_CHANGE', {
@@ -939,7 +941,6 @@ define([
};
// Cursor
-
Store.cursor = {
execCommand: function (clientId, data, cb) {
if (!store.cursor) { return void cb ({error: 'Cursor channel is disabled'}); }
@@ -947,6 +948,14 @@ define([
}
};
+ // Mailbox
+ Store.mailbox = {
+ execCommand: function (clientId, data, cb) {
+ if (!store.mailbox) { return void cb ({error: 'Mailbox is disabled'}); }
+ store.mailbox.execCommand(clientId, data, cb);
+ }
+ };
+
// Admin
Store.adminRpc = function (clientId, data, cb) {
store.rpc.adminRpc(data, function (err, res) {
@@ -1010,7 +1019,7 @@ define([
});
channel.history.forEach(function (msg) {
postMessage(clientId, "PAD_MESSAGE", {
- msg: CpNfWorker.removeCp(msg),
+ msg: CpNetflux.removeCp(msg),
user: channel.wc.myID,
validateKey: channel.data.validateKey
});
@@ -1020,14 +1029,15 @@ define([
return;
}
var conf = {
- onReady: function (padData) {
- channel.data = padData || {};
+ onReady: function (pad) {
+ var padData = pad.metadata || {};
+ channel.data = padData;
if (padData && padData.validateKey && store.messenger) {
store.messenger.storeValidateKey(data.channel, padData.validateKey);
}
postMessage(clientId, "PAD_READY");
},
- onMessage: function (user, m, validateKey, isCp) {
+ onMessage: function (m, user, validateKey, isCp) {
channel.pushHistory(m, isCp);
channel.bcast("PAD_MESSAGE", {
user: user,
@@ -1041,13 +1051,25 @@ define([
onLeave: function (m) {
channel.bcast("PAD_LEAVE", m);
},
- onDisconnect: function () {
+ onAbort: function () {
channel.bcast("PAD_DISCONNECT");
},
onError: function (err) {
channel.bcast("PAD_ERROR", err);
- delete channels[data.channel]; // TODO test?
+ delete channels[data.channel];
+ },
+ onChannelError: function (err) {
+ channel.bcast("PAD_ERROR", err);
+ delete channels[data.channel];
+ },
+ onConnectionChange: function () {},
+ crypto: {
+ // The encryption and decryption is done in the outer window.
+ // This async-store only deals with already encrypted messages.
+ encrypt: function (m) { return m; },
+ decrypt: function (m) { return m; }
},
+ noChainPad: true,
channel: data.channel,
validateKey: data.validateKey,
owners: data.owners,
@@ -1060,10 +1082,10 @@ define([
// Send to server
sendMessage(msg, function () {
// Broadcast to other tabs
- channel.pushHistory(CpNfWorker.removeCp(msg), /^cp\|/.test(msg));
+ channel.pushHistory(CpNetflux.removeCp(msg), /^cp\|/.test(msg));
channel.bcast("PAD_MESSAGE", {
user: wc.myID,
- msg: CpNfWorker.removeCp(msg),
+ msg: CpNetflux.removeCp(msg),
validateKey: channel.data.validateKey
}, cId);
cb();
@@ -1073,6 +1095,7 @@ define([
channel.queue.forEach(function (data) {
channel.sendMessage(data.message, clientId);
});
+ channel.queue = [];
channel.bcast("PAD_CONNECT", {
myID: wc.myID,
id: wc.id,
@@ -1080,7 +1103,7 @@ define([
});
}
};
- channel.cpNf = CpNfWorker.start(conf);
+ channel.cpNf = CpNetflux.start(conf);
};
Store.leavePad = function (clientId, data, cb) {
var channel = channels[data.channel];
@@ -1291,6 +1314,7 @@ define([
if (messengerIdx !== -1) {
messengerEventClients.splice(messengerIdx, 1);
}
+ // TODO mailbox events
try {
store.cursor.removeClient(clientId);
} catch (e) { console.error(e); }
@@ -1392,6 +1416,17 @@ define([
});
};
+ var loadMailbox = function (waitFor) {
+ store.mailbox = Mailbox.init(store, waitFor, function (ev, data, clients) {
+ clients.forEach(function (cId) {
+ postMessage(cId, 'MAILBOX_EVENT', {
+ ev: ev,
+ data: data
+ });
+ });
+ });
+ };
+
//////////////////////////////////////////////////////////////////
/////////////////////// Init /////////////////////////////////////
//////////////////////////////////////////////////////////////////
@@ -1486,6 +1521,7 @@ define([
loadMessenger();
loadCursor();
loadOnlyOffice();
+ loadMailbox(waitFor);
}).nThen(function () {
var requestLogin = function () {
broadcast([], "REQUEST_LOGIN");
diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js
deleted file mode 100644
index 6ac013d51..000000000
--- a/www/common/outer/chainpad-netflux-worker.js
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright 2014 XWiki SAS
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-define([], function () {
- var USE_HISTORY = true;
-
- var verbose = function (x) { console.log(x); };
- verbose = function () {}; // comment out to enable verbose logging
-
- var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
-
- var removeCp = function (str) {
- return str.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, '');
- };
-
- var start = function (conf) {
- var channel = conf.channel;
- var validateKey = conf.validateKey;
- var readOnly = conf.readOnly || false;
- var network = conf.network;
- var onConnect = conf.onConnect || function () { };
- var onMessage = conf.onMessage;
- var onJoin = conf.onJoin;
- var onLeave = conf.onLeave;
- var onReady = conf.onReady;
- var onDisconnect = conf.onDisconnect;
- var onError = conf.onError;
- var owners = conf.owners;
- var password = conf.password;
- var expire = conf.expire;
- var padData;
- conf = undefined;
-
- var initializing = true;
- var stopped = false;
- var lastKnownHash;
-
- var messageFromOuter = function () {};
-
- var error = function (err, wc) {
- if (onError) {
- onError({
- type: err,
- loaded: !initializing
- });
- if (wc && (err === "EEXPIRED" || err === "EDELETED")) { wc.leave(); }
- }
- else { console.error(err); }
- };
-
- var onRdy = function (padData) {
- // Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
- // message through "network" when it is synced, and it triggers onReady for each channel joined.
- if (!initializing) { return; }
- onReady(padData);
- //sframeChan.event('EV_RT_READY', null);
- // we're fully synced
- initializing = false;
- };
-
- // shim between chainpad and netflux
- var msgIn = function (peerId, msg) {
- // NOTE: Hash version 0 contains a 32 characters nonce followed by a pipe
- // at the beginning of each message on the server.
- // We have to make sure our regex ignores this nonce using {0,20} (our IDs
- // should only be 8 characters long)
- return removeCp(msg);
- };
-
- var msgOut = function (msg) {
- if (readOnly) { return; }
- return msg;
- };
-
- var onMsg = function(peer, msg, wc, network, direct) {
- // unpack the history keeper from the webchannel
- var hk = network.historyKeeper;
-
- if (direct && peer !== hk) {
- return;
- }
- if (direct) {
- var parsed = JSON.parse(msg);
- if (parsed.validateKey && parsed.channel) {
- if (parsed.channel === wc.id && !validateKey) {
- validateKey = parsed.validateKey;
- }
- if (parsed.channel === wc.id) {
- padData = parsed;
- }
- // We have to return even if it is not the current channel:
- // we don't want to continue with other channels messages here
- return;
- }
- if (parsed.state && parsed.state === 1 && parsed.channel) {
- if (parsed.channel === wc.id) {
- onRdy(padData);
- }
- // We have to return even if it is not the current channel:
- // we don't want to continue with other channels messages here
- return;
- }
- }
- if (peer === hk) {
- // if the peer is the 'history keeper', extract their message
- var parsed1 = JSON.parse(msg);
- // First check if it is an error message (EXPIRED/DELETED)
- if (parsed1.channel === wc.id && parsed1.error) {
- return void error(parsed1.error, wc);
- }
-
- msg = parsed1[4];
- // Check that this is a message for our channel
- if (parsed1[3] !== wc.id) { return; }
- }
-
-
- lastKnownHash = msg.slice(0,64);
-
- var isCp = /^cp\|/.test(msg);
- var message = msgIn(peer, msg);
-
- verbose(message);
-
- // slice off the bencoded header
- // Why are we getting bencoded stuff to begin with?
- // FIXME this shouldn't be necessary
- message = unBencode(message);//.slice(message.indexOf(':[') + 1);
-
- // pass the message into Chainpad
- onMessage(peer, message, validateKey, isCp);
- //sframeChan.query('Q_RT_MESSAGE', message, function () { });
- };
-
- // We use an object to store the webchannel so that we don't have to push new handlers to chainpad
- // and remove the old ones when reconnecting and keeping the same 'realtime' object
- // See realtime.onMessage below: we call wc.bcast(...) but wc may change
- var wcObject = {};
- var onOpen = function(wc, network, firstConnection) {
- wcObject.wc = wc;
- channel = wc.id;
-
- // Add the existing peers in the userList
- //TODO sframeChan.event('EV_RT_CONNECT', { myID: wc.myID, members: wc.members, readOnly: readOnly });
-
- // Add the handlers to the WebChannel
- wc.on('message', function (msg, sender) { //Channel msg
- onMsg(sender, msg, wc, network);
- });
- wc.on('join', function (m) { onJoin(m); /*sframeChan.event('EV_RT_JOIN', m);*/ });
- wc.on('leave', function (m) { onLeave(m); /*sframeChan.event('EV_RT_LEAVE', m);*/ });
-
- if (firstConnection) {
- // Sending a message...
- messageFromOuter = function(message, cb) {
- // Filter messages sent by Chainpad to make it compatible with Netflux
- message = msgOut(message);
- if (message) {
- // Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we
- // want to keep the same chainpad (realtime) object
- try {
- wcObject.wc.bcast(message).then(function() {
- cb();
- }, function(err) {
- // The message has not been sent, display the error.
- console.error(err);
- });
- } catch (e) {
- console.log(e);
- // Just skip calling back and it will fail on the inside.
- }
- }
- };
- }
-
- onConnect(wc, messageFromOuter);
-
- // Get the channel history
- if (USE_HISTORY) {
- var hk;
-
- wc.members.forEach(function (p) {
- if (p.length === 16) { hk = p; }
- });
- network.historyKeeper = hk;
-
- var cfg = {
- validateKey: validateKey,
- lastKnownHash: lastKnownHash,
- owners: owners,
- expire: expire,
- password: password
- };
- var msg = ['GET_HISTORY', wc.id, cfg];
- // Add the validateKey if we are the channel creator and we have a validateKey
- if (hk) {
- network.sendto(hk, JSON.stringify(msg)).then(function () {
- }, function (err) {
- console.error(err);
- });
- }
- } else {
- onRdy();
- }
- };
-
- /*var isIntentionallyLeaving = false;
- window.addEventListener("beforeunload", function () {
- isIntentionallyLeaving = true;
- });*/
-
- var findChannelById = function (webChannels, channelId) {
- var webChannel;
-
- // Array.some terminates once a truthy value is returned
- // best case is faster than forEach, though webchannel arrays seem
- // to consistently have a length of 1
- webChannels.some(function(chan) {
- if(chan.id === channelId) { webChannel = chan; return true;}
- });
- return webChannel;
- };
-
- var connectTo = function (network, firstConnection) {
- // join the netflux network, promise to handle opening of the channel
- network.join(channel || null).then(function(wc) {
- onOpen(wc, network, firstConnection);
- }, function(err) {
- console.error(err);
- if (onError) {
- onError({
- type: err && (err.type || err),
- loaded: !initializing
- });
- }
- });
- };
-
- network.on('disconnect', function (reason) {
- //if (isIntentionallyLeaving) { return; }
- if (reason === "network.disconnect() called") { return; }
- onDisconnect();
- //sframeChan.event('EV_RT_DISCONNECT');
- });
-
- network.on('reconnect', function () {
- if (stopped) { return; }
- initializing = true;
- connectTo(network, false);
- });
-
- network.on('message', function (msg, sender) { // Direct message
- if (stopped) { return; }
- var wchan = findChannelById(network.webChannels, channel);
- if (wchan) {
- onMsg(sender, msg, wchan, network, true);
- }
- });
-
- connectTo(network, true);
-
- return {
- stop: function () {
- var wchan = findChannelById(network.webChannels, channel);
- if (wchan) { wchan.leave(''); }
- stopped = true;
- }
- };
- };
-
- return {
- start: start,
- removeCp: removeCp
- /*function (config) {
- config.sframeChan.whenReg('EV_RT_READY', function () {
- start(config);
- });
- }*/
- };
-});
-
diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js
new file mode 100644
index 000000000..313690a29
--- /dev/null
+++ b/www/common/outer/mailbox.js
@@ -0,0 +1,36 @@
+// jshint ignore: start
+define([
+ '/common/common-util.js',
+ '/common/common-constants.js',
+ '/customize/messages.js',
+ '/bower_components/chainpad-netflux/chainpad-netflux.js',
+ '/bower_components/chainpad-crypto/crypto.js',
+], function (Util, Constants, Messages, CpNetflux, Crypto) {
+ var Mailbox = {};
+
+ Mailbox.init = function (store, waitFor, emit) {
+ var mailbox = {};
+ var ctx = {
+ store: store,
+ emit: emit,
+ };
+
+ mailbox.removeClient = function (clientId) {
+ // TODO
+ //removeClient(ctx, clientId);
+ };
+ mailbox.leavePad = function (padChan) {
+ // TODO
+ //leaveChannel(ctx, padChan);
+ };
+ mailbox.execCommand = function (clientId, obj, cb) {
+ var cmd = obj.cmd;
+ var data = obj.data;
+ };
+
+ return mailbox;
+ };
+
+ return Mailbox;
+});
+
diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js
index 49bb7c960..aed0d5ab4 100644
--- a/www/common/outer/store-rpc.js
+++ b/www/common/outer/store-rpc.js
@@ -66,6 +66,8 @@ define([
OO_COMMAND: Store.onlyoffice.execCommand,
// Cursor
CURSOR_COMMAND: Store.cursor.execCommand,
+ // Mailbox
+ MAILBOX_COMMAND: Store.mailbox.execCommand,
// Pad
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,
diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js
index 46ae72b3b..10e30ba95 100644
--- a/www/common/outer/userObject.js
+++ b/www/common/outer/userObject.js
@@ -636,7 +636,7 @@ define([
}
// If we have an edit link, check the view link
- if (el.href && parsed.hashData.type === "pad") {
+ if (el.href && parsed.hashData.type === "pad" && parsed.hashData.version) {
if (parsed.hashData.mode === "view") {
el.roHref = el.href;
delete el.href;
@@ -651,6 +651,10 @@ define([
}
}
}
+ // v0 hashes don't support read-only
+ if (parsed.hashData.version === 0) {
+ delete el.roHref;
+ }
// Fix href
if (el.href && /^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js
index d23ab3ebc..a933da776 100644
--- a/www/common/proxy-manager.js
+++ b/www/common/proxy-manager.js
@@ -961,7 +961,20 @@ define([
};
var getRecentPads = function (Env) {
- return Env.user.userObject.getRecentPads();
+ var files = [];
+ var userObjects = _getUserObjects(Env);
+ userObjects.forEach(function (uo) {
+ var data = uo.getFiles([UserObject.FILES_DATA]).map(function (id) {
+ return [Number(id), uo.getFileData(id)];
+ });
+ Array.prototype.push.apply(files, data);
+ });
+ var sorted = files.filter(function (a) { return a[1].atime; })
+ .sort(function (a,b) {
+ return b[1].atime - a[1].atime;
+ });
+ return sorted;
+ //return Env.user.userObject.getRecentPads();
};
var getOwnedPads = function (Env) {
return Env.user.userObject.getOwnedPads(Env.edPublic);
diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json
index 0abb1f49e..603ad53b0 100644
--- a/www/common/translations/messages.de.json
+++ b/www/common/translations/messages.de.json
@@ -1041,5 +1041,15 @@
"contact_devHint": "Für Verbesserungsvorschläge oder zum Danke-Sagen.",
"contact_bug": "Fehlerbericht",
"contact_chat": "Chat",
- "contact_email": "E-Mail"
+ "contact_email": "E-Mail",
+ "timeoutError": "Ein Fehler hat deine Verbindung zum Server unterbrochen.
Drücke Esc, um die Seite neu zu laden.",
+ "admin_diskUsageTitle": "Speicherplatzbelegung",
+ "admin_diskUsageHint": "Speicherplatz, der von verschiedenen CryptPad-Ressourcen verwendet wird",
+ "admin_diskUsageButton": "Bericht generieren",
+ "settings_codeSpellcheckTitle": "Rechtschreibprüfung",
+ "settings_codeSpellcheckLabel": "Rechtschreibprüfung im Code-Editor aktivieren",
+ "drive_active1Day": "Letzte 24 Stunden",
+ "drive_active7Days": "Letzte 7 Tage",
+ "drive_active28Days": "Letzte 4 Wochen",
+ "drive_activeOld": "Ältere Pads"
}
diff --git a/www/common/translations/messages.es.json b/www/common/translations/messages.es.json
index 4bb98225c..5225f4ce8 100644
--- a/www/common/translations/messages.es.json
+++ b/www/common/translations/messages.es.json
@@ -10,10 +10,12 @@
"kanban": "Kanban",
"drive": "CryptDrive",
"todo": "Lista de tareas",
- "file": "Archivo"
+ "file": "Archivo",
+ "media": "Media",
+ "sheet": "Hoja (Beta)"
},
"disconnected": "Desconectado",
- "synchronizing": "Sincronización",
+ "synchronizing": "Sincronizando",
"reconnecting": "Reconectando...",
"lag": "Retraso",
"readonly": "Sólo lectura",
@@ -373,16 +375,16 @@
"profile_urlPlaceholder": "URL",
"profile_namePlaceholder": "Nombre mostrado en su perfil",
"profile_avatar": "Imagen",
- "profile_upload": "Subir una imagen",
+ "profile_upload": "Subir una imagen de perfil",
"profile_error": "Error al crear tu perfil: {0}",
- "profile_register": "Tienes que registrarte para crear un perfil",
+ "profile_register": "Tienes que ingresar para crear un perfil!",
"profile_create": "Crear perfil",
"profile_description": "Descripción",
"profile_fieldSaved": "Guardado: {0}",
"download_mt_button": "Descargar",
"updated_0_header_logoTitle": "Volver a tu CryptDrive",
"realtime_unrecoverableError": "El motor de tiempo real ha encontrado un error. Haga clic en OK para recargar la página.",
- "typing": "Escribiendo",
+ "typing": "Editando",
"profile_inviteButton": "Conectar",
"profile_inviteButtonTitle": "Crear un enlace de invitación para este usuario.",
"profile_inviteExplanation": "Hacer clic en OK creará un enlace de mensaje seguro que sólo {0} podrá ver.
El enlace será copiado a tu portapapeles y puede ser compartido públicamente.",
@@ -435,5 +437,129 @@
"settings_codeUseTabs": "Utilizar tabulaciones en vez de espacios",
"pad_showToolbar": "Mostrar la barra de herramientas",
"pad_hideToolbar": "Esconder la barra de herramientas",
- "main_catch_phrase": "El Cloud Zero Knowledge"
+ "main_catch_phrase": "El Cloud Zero Knowledge",
+ "button_newkanban": "Nuevo Kanban",
+ "button_newsheet": "Nueva Hoja",
+ "padNotPinned": "Esta nota expirará luego de 3 meses de inactividad, {0}ingresar{1} o {2}registrarse{3}para conservar",
+ "anonymousStoreDisabled": "El webmaster de esta instancia de CryptPad a deshabilitado al almacenamiento para usuarios anónimos. Debes ingresar para poder usar CrytDrive.",
+ "expiredError": "Este pad ha expirado y ya no está disponible",
+ "deletedError": "Esta nota ha sido borrada por su dueño y ya no está disponible.",
+ "inactiveError": "Esta nota ha sido eliminada por inactividad. Presione Esc para crear una nueva nota.",
+ "chainpadError": "Ha ocurrido un error crítico al actualizar su contenido. Esta página esta en modo de sólo lectura, para asegurarse que no perderá su trabajo.
HitEscpara continuar y ver esta nota, o recargar para editar nuevamente.",
+ "invalidHashError": "El documento que has solicitado tiene una URL invalida.",
+ "errorCopy": " Aún puedes copiar el contenido a otra ubicación apretando Esc.
Una vez que dejes esta página desaparecerá para siempre!",
+ "errorRedirectToHome": "PresionaEscpara ser redirigido a tu Cryptdrive.",
+ "newVersionError": "Una nueva versión de CryptPad está disponible.
Recargar para usar la nueva versión, o presiona escape para acceder a tu contenido en modo offline>.",
+ "deletedFromServer": "Nota borrada desde el servidor",
+ "mustLogin": "Debes haber ingresado a tu cuenta para acceder a esta página",
+ "disabledApp": "Esta nota ha sido eliminada por inactividad. Presione Esc para crear una nueva nota.",
+ "initializing": "Iniciando...",
+ "forgotten": "Movido a la basura",
+ "errorState": "Error crítico: {0}",
+ "userlist_offline": "Actualmente estás desconectado, la lista del usuario ya no está disponible.",
+ "chatButton": "Chat",
+ "useTemplate": "Comenzar con una plantilla?",
+ "useTemplateOK": "Elija una plantilla (Enter)",
+ "template_import": "Importar una plantilla",
+ "template_empty": "No hay plantillas disponibles",
+ "propertiesButton": "Propiedades",
+ "propertiesButtonTitle": "Obtener las propiedades de esta nota",
+ "printButtonTitle2": "Imprimir el documento o exportar como archivo PDF",
+ "printBackground": "Usar una imagen de fondo",
+ "printBackgroundButton": "Elija una imagen",
+ "printBackgroundValue": "Fondo de pantalla actual{0}",
+ "printBackgroundRemove": "Eliminar este fondo de pantalla",
+ "tags_title": "Etiquetas (sólo para tí)",
+ "tags_add": "Actualizar las etiquetas de esta página",
+ "tags_searchHint": "Comenzar una búsqueda con # en tú CryptDrive para encontrar las notas etiquetadas",
+ "tags_notShared": "Tus etiquetas no están compartidas con otros usuarios",
+ "tags_duplicate": "Duplicar etiquetas:{0}",
+ "tags_noentry": "No puedes etiquetar una nota eliminada!",
+ "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",
+ "history_loadMore": "Cargar más historial",
+ "pad_mediatagWidth": "Ancho (px)",
+ "pad_mediatagHeight": "Altura (px)",
+ "pad_mediatagRatio": "Mantener proporción",
+ "pad_mediatagBorder": "Ancho del borde (px)",
+ "pad_mediatagPreview": "Previsualizar",
+ "pad_mediatagImport": "Grabar en su CryptDrive",
+ "pad_mediatagOptions": "Propiedades de la Imagen",
+ "kanban_newBoard": "Nueva Pizarra",
+ "kanban_item": "Item {0}",
+ "kanban_todo": "Por hacer",
+ "kanban_done": "Hecho",
+ "kanban_working": "En progreso",
+ "kanban_deleteBoard": "Estás seguro que quieres eliminar esta pizarra?",
+ "kanban_addBoard": "Agregar una pizarra",
+ "kanban_removeItem": "Remover este item",
+ "kanban_removeItemConfirm": "Estás seguro que quieres eliminar este ítem?",
+ "printBackgroundNoValue": "No se muestra fondo de pantalla",
+ "getEmbedCode": "Obtener el código insertado",
+ "viewEmbedTitle": "Insertar la nota en una página externa",
+ "viewEmbedTag": "Para insertar esta nota, incluya este iframe en su página donde usted quiera. Puede darle estilo usando CSS o atributos HTML.",
+ "fileEmbedTitle": "Insertar el archivo en una pagina externa",
+ "fileEmbedScript": "Para insertar este archivo, incluya este código una vez en su página para cargar el Etiqueta de Media:",
+ "fileEmbedTag": "Luego ponga esta Etiqueta de Media donde quiera que sea incorporada en su página: ",
+ "pad_mediatagTitle": "Opciones de Etiquetas de Media",
+ "poll_bookmark_col": "Agregue esta columna a sus marcadores así estará siempre desbloqueada y se le mostrará al comienzo",
+ "poll_bookmarked_col": "Esta es su columna de bookmarks. Siempre será desbloqueada y mostrada al comienzo para usted.",
+ "poll_total": "TOTAL",
+ "poll_comment_list": "Comentarios",
+ "poll_comment_add": "Agregue un comentario",
+ "poll_comment_submit": "Enviar",
+ "poll_comment_remove": "Elimine este comentario",
+ "poll_comment_placeholder": "Su comentario",
+ "poll_comment_disabled": "Publique esta encuesta usando el botón ✓ para habilitar los comentarios.",
+ "oo_reconnect": "La conexión con el servidor se ha restablecido. Haga clic en OK para recargar y continúe con la edición.",
+ "oo_cantUpload": "Subir no está permitido mientras usuarios están presentes.",
+ "oo_uploaded": "La subida de archivos se ha completado. Clic en OK para recargar la página o cancelar para continuar en modo de solo lectura.",
+ "canvas_imageEmbed": "Añada una imagen desde su computador",
+ "profile_uploadSizeError": "Error: su imagen de perfil debe ser menor que {0}",
+ "profile_uploadTypeError": "Error: su imagen de perfil no es permitida. Los tipos permitidos son: {0}",
+ "contacts_warning": "Todo lo que escribas acá es persistente y estará disponible para todo los usuarios de esta nota. Sea precavido con la información sensible e importante!",
+ "contacts_padTitle": "Chat",
+ "contacts_fetchHistory": "Recuperar la historial antigua",
+ "contacts_friends": "Amigos",
+ "contacts_rooms": "Sala de chat",
+ "contacts_leaveRoom": "Deja esta sala",
+ "contacts_online": "Otro usuario de esta sala está online",
+ "debug_getGraph": "Obtenga el código para generar un grafico de este documento",
+ "debug_getGraphWait": "Generando el gráfico... Por favor espere.",
+ "debug_getGraphText": "Este es el código DOT para generar un gráfico del historial de este documento:",
+ "fm_recentPadsName": "Notas recientes",
+ "fm_ownedPadsName": "Dueño",
+ "fm_tagsName": "Etiquetas",
+ "fm_sharedFolderName": "Carpeta compartida",
+ "fm_sharedFolder": "Carpeta compartida",
+ "fm_removePermanentlyNote": "Sus notas serán removidas del servidor si continua.",
+ "fm_deleteOwnedPad": "Está seguro que quiere remover esta nota del servidor de manera permanente?",
+ "fm_deleteOwnedPads": "Está seguro que quiere remover estas notas del servidor de manera permanente?",
+ "fm_info_recent": "Lista de notas recientemente abiertas o modificadas.",
+ "fm_info_sharedFolder": "Esta es una carpeta compartida. Usted no ha ingresado a su cuenta por lo que solo tiene acceso en modo solo lectura.
ingresar o ingresar para poder importarla a su Cryptdrive y modificarla.",
+ "fm_info_owned": "Usted es el dueño de las notas que se presentan. Esto significa que puede removerlas de manera permanente del servidor cuando lo desee. Si lo hace, los otros usuarios no podrán acceder a ellas nunca más.",
+ "fm_renamedPad": "Ha definido un nombre personalizado para esta nota. El título que se comparte es: br>{0}",
+ "fm_canBeShared": "Esta carpeta puede ser compartida",
+ "fm_prop_tagsList": "Etiquetas",
+ "fm_burnThisDriveButton": "Borre toda la información que ha sido almacenada por CryptPad en su navegador",
+ "fm_burnThisDrive": "Está seguro que quiere remover todo lo que ha sido almacenado por CryptPad en su navegador?
Esto removerá su CryptDrive y su historial de su navegador, pero sus notas aun existirán (encriptadas) en nuestro servidor.",
+ "fm_padIsOwned": "Usted es el dueño de esta nota",
+ "fm_padIsOwnedOther": "El dueño de esta nota es otro usuario",
+ "fm_deletedPads": "Estas notas ya no existen en este servidor, han sido removidas desde su CryptDrive: {0}",
+ "fm_tags_name": "Nombre de la Etiqueta",
+ "fm_tags_used": "Número de usos",
+ "fm_restoreDrive": "Su drive se está reseteando a un estado anterior. Para mejores resultados evite hacer cambios a su drive hasta que el proceso se haya completado.",
+ "fm_moveNestedSF": "No puedes poner una carpeta compartida dentro de otra. La carpeta {0} no se movió.",
+ "fm_passwordProtected": "Este documento está protegido por una contraseña",
+ "fc_newsharedfolder": "Nueva carpeta compartida",
+ "fc_delete_owned": "Eliminar desde el servidor",
+ "fc_remove_sharedfolder": "Eliminar",
+ "fc_hashtag": "Etiquetas",
+ "register_passwordTooShort": "La contraseña debe tener por los menos {0} caracteres de largo.",
+ "useTemplateCancel": "Recomenzar (Esc)"
}
diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json
index 2d69fc534..1393dd470 100644
--- a/www/common/translations/messages.fr.json
+++ b/www/common/translations/messages.fr.json
@@ -1047,5 +1047,9 @@
"admin_diskUsageHint": "Quantité d'espace de stockage utilisé par la base de données de CryptPad",
"admin_diskUsageButton": "Générer le rapport",
"settings_codeSpellcheckTitle": "Vérification orthographique",
- "settings_codeSpellcheckLabel": "Activer la vérification orthographique"
+ "settings_codeSpellcheckLabel": "Activer la vérification orthographique",
+ "drive_active1Day": "Dernières 24 heures",
+ "drive_active7Days": "7 derniers jours",
+ "drive_active28Days": "4 dernières semaines",
+ "drive_activeOld": "Pads moins récents"
}
diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json
index 5a37bd222..6b4463892 100644
--- a/www/common/translations/messages.it.json
+++ b/www/common/translations/messages.it.json
@@ -358,5 +358,74 @@
"fm_info_template": "Contiene tutti i pads salvati come modelli e che puoi riutilizzare per creare nuovi pad.",
"fm_info_recent": "Lista dei pads recentemente aperti o modificati.",
"fm_info_trash": "Svuota il tuo cestino per liberare spazio nel tuo CryptDrive.",
- "fm_info_allFiles": "Contiene tutti i files da \"Documenti\", \"Non catalogati\", \"Cestino\". Non puoi muovere o cancellare files da qui."
+ "fm_info_allFiles": "Contiene tutti i files da \"Documenti\", \"Non catalogati\", \"Cestino\". Non puoi muovere o cancellare files da qui.",
+ "fm_info_anonymous": "Non sei loggato, quindi i tuoi pads scadranno tra tre mesi (find out more). Sono salvati nel tuo browser, quindi cancellando la cronologia potresti farli scomparire.
Registrati o Effettua il login per salvarti permanentemente.
",
+ "fm_info_sharedFolder": "Questa è una cartella condivisa. Non sei loggato, quindi puoi accederla solo in modalità solo lettura.
Registrati o Effettua il login per poterla importare nel tuo CryptDrive e per modificarla.",
+ "fm_info_owned": "Sei il proprietario dei pads mostrati qui. Questo significa che puoi rimuoverli permanentemente dal server, quando lo desideri. Se lo fai, gli altri utenti non potranno mai più accedervi.",
+ "fm_alert_backupUrl": "Link di backup per questo drive.
Èestremamente raccomandato che tu lo tenga segreto.
Puoi usarlo per recuperare tutti i tuoi files nel caso in cui la memoria del tuo browser venga cancellata.
Chiunque in possesso di questo link può modificare o rimuovere tutti i files nel tuo file manager.
",
+ "fm_backup_title": "Link di backup",
+ "fm_nameFile": "Come vuoi chiamare questo file?",
+ "fm_error_cantPin": "Errore interno del server. Per favore, ricarica la pagina e prova di nuovo.",
+ "fm_viewListButton": "Visualizzazione lista",
+ "fm_viewGridButton": "Visualizzazione griglia",
+ "fm_renamedPad": "Hai impostato un nome personalizzato per questo pad. Il suo titolo condiviso è:
{0}",
+ "fm_canBeShared": "Questa cartella può essere condivisa",
+ "fm_prop_tagsList": "Tags",
+ "fm_burnThisDriveButton": "Cancella tutte le informazioni salvate da CryptPad nel tuo browser",
+ "fm_burnThisDrive": "Sei sicuro di voler rimuovere tutti i dati salvati da CryptPad nel tuo browser?
Questo rimuoverà il tuo CryptDrive e la sua cronologia dal tuo browser, ma i tuoi pad continueranno a esistere (criptati) nel nostro server.",
+ "fm_padIsOwned": "Sei il proprietario di questo pad",
+ "fm_padIsOwnedOther": "Il proprietario di questo pad è un altro user",
+ "fm_deletedPads": "Questi pads non esistono più sul server, sono stati rimossi dal tuo CryptDrive: {0}",
+ "fm_tags_name": "Nome del tag",
+ "fm_tags_used": "Numero di utilizzi",
+ "fm_restoreDrive": "Ripristina il tuo drive ad uno stato precedente. Per i migliori risultati, evita di fare cambiamenti al tuo drive sinchè questo processo non sarà completato.",
+ "fm_moveNestedSF": "Non puoi spostare una cartella condivisa all'interno di un'altra. La cartella {0} non è stata spostata.",
+ "fm_passwordProtected": "Questo documento è protetto da una password",
+ "fc_newfolder": "Nuova cartella",
+ "fc_newsharedfolder": "Nuova cartella condivisa",
+ "fc_rename": "Rinomina",
+ "fc_open": "Apri",
+ "fc_open_ro": "Apri (solo lettura)",
+ "fc_delete": "Muovi nel cestino",
+ "fc_delete_owned": "Cancella dal server",
+ "fc_restore": "Ripristina",
+ "fc_remove": "Rimuovi dal tuo CryptDrive",
+ "fc_remove_sharedfolder": "Rimuovi",
+ "fc_empty": "Svuota il cestino",
+ "fc_prop": "Proprietà",
+ "fc_hashtag": "Tags",
+ "fc_sizeInKilobytes": "Dimensione in Kilobytes",
+ "fo_moveUnsortedError": "Non puoi muovere una cartella nella lista dei modelli",
+ "fo_existingNameError": "Il nome è già utilizzato in questa cartella. Per favore, scegline un altro.",
+ "fo_moveFolderToChildError": "Non puoi muovere una cartella in una contenuta in essa",
+ "fo_unableToRestore": "Impossibile ripristinare questo file nella sua location originaria. Puoi provare a muoverlo in una nuova posizione.",
+ "fo_unavailableName": "Un file o una cartella con lo stesso nome esiste già nella nuova posizione. Rinomina l'elemento e prova di nuovo.",
+ "fs_migration": "Il tuo CryptDrive sta per essere aggiornato a una nuova versione. La pagina corrente sarà ricaricata.
Per favore, ricarica la pagina per continuare a usarla.",
+ "login_login": "Login",
+ "login_makeAPad": "Crea un pad in maniera anonima",
+ "login_nologin": "Sfoglia i pads locali",
+ "login_register": "Registrati",
+ "logoutButton": "Logout",
+ "settingsButton": "Impostazioni",
+ "login_username": "Nome utente",
+ "login_password": "Password",
+ "login_confirm": "Conferma la tua password",
+ "login_remember": "Ricorda l'accesso",
+ "login_hashing": "Effettuo l'hash della tua password, questo può richiedere del tempo.",
+ "login_hello": "Buongiorno {0},",
+ "login_helloNoName": "Ciao,",
+ "login_accessDrive": "Accedi al tuo drive",
+ "login_orNoLogin": "o",
+ "login_noSuchUser": "Nome utente o password non validi. Prova di nuovi, oppure registrati",
+ "login_invalUser": "Nome utente richiesto",
+ "login_invalPass": "Password richiesta",
+ "login_unhandledError": "Si è verificato un errore inaspettato :(",
+ "register_importRecent": "Importa i pads dalla tua sessione anonima",
+ "register_acceptTerms": "Accetto i termini del servizio",
+ "register_passwordsDontMatch": "Le passwords non corrispondono!",
+ "register_passwordTooShort": "Le passwords devono essere lunghe almeno {0} caratteri.",
+ "register_mustAcceptTerms": "Devi accettare i termini del servizio.",
+ "register_mustRememberPass": "Non possiamo ripristinare la tua password se la dimentichi. È estremamente importante che tu la ricordi! Per favore, spunta la checkbox per confermare.",
+ "register_whyRegister": "Perché registrarsi?",
+ "register_header": "Benvenuto su CryptPad"
}
diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json
index 375f24dd7..9b7fc7281 100644
--- a/www/common/translations/messages.json
+++ b/www/common/translations/messages.json
@@ -1047,5 +1047,9 @@
"admin_diskUsageHint": "Amount of storage space consumed by various CryptPad resources",
"admin_diskUsageButton": "Generate report",
"settings_codeSpellcheckTitle": "Spellcheck",
- "settings_codeSpellcheckLabel": "Enable spellcheck in the code editor"
+ "settings_codeSpellcheckLabel": "Enable spellcheck in the code editor",
+ "drive_active1Day": "Last 24 hours",
+ "drive_active7Days": "Last 7 days",
+ "drive_active28Days": "Last 4 weeks",
+ "drive_activeOld": "Less recent pads"
}
diff --git a/www/common/translations/messages.ro.json b/www/common/translations/messages.ro.json
index 5e66d7287..376fb3636 100644
--- a/www/common/translations/messages.ro.json
+++ b/www/common/translations/messages.ro.json
@@ -514,5 +514,45 @@
"fm_padIsOwnedOther": "Acest pad îi aparține altui utilizator",
"fm_deletedPads": "Aceste pad-uri nu mai există pe server, au fost șterse din CryptDrive: {0}",
"fm_tags_name": "Numele etichetei",
- "fm_tags_used": "Numărul de utilizări"
+ "fm_tags_used": "Numărul de utilizări",
+ "fm_restoreDrive": "Drive-ul tău va fi setat la o versiune aneterioară. Pentru a obține cele mai bune rezultate, te rugăm să eviți să aduci modificări în drive-ul tău până când procesul va fi terminat.",
+ "fm_moveNestedSF": "Nu poti plasa un folder partajat în interiorul altuia. Folderul {0} nu a fost mutat.",
+ "fm_passwordProtected": "Acest document este protejat cu o parolă",
+ "fc_newsharedfolder": "Folder partajat nou",
+ "fc_delete_owned": "Șterge de pe server",
+ "fc_remove_sharedfolder": "Șterge",
+ "fc_hashtag": "Etichete",
+ "fs_migration": "CrytpDrive-ul tău este updatat la o versiune nouă. În consecință, pagina curentă trebuie reîncarcată. Te rugăm să reîncarci această pagina pentru a continua să o utilizezi.",
+ "register_passwordTooShort": "Parolele trebuie să aibă o lungime de cel putin {0} caractere.",
+ "register_whyRegister": "De ce să te înscrii?",
+ "settings_cat_account": "Cont",
+ "settings_cat_drive": "CrytpDrive",
+ "settings_cat_cursor": "Cursor",
+ "settings_cat_code": "Cod",
+ "settings_cat_pad": "Rich text",
+ "settings_cat_creation": "Pad nou",
+ "settings_cat_subscription": "Abonare",
+ "settings_backupCategory": "Backup",
+ "settings_backupHint": "Fă backup sau restaurează tot conținutul CryptDrive-ului tău. Aceasta nu va însemna conținutul pad-urilor tale, ci doar modalitatea de a le accesa",
+ "settings_backupHint2": "Descarcă conținutul actual al pad-urilor tale. Ele vor fi descărcate într-un format care poate fi citit, dacă un astfel de format este disponibil.",
+ "settings_backup2": "Descarcă-mi CryptDrive-ul",
+ "settings_backup2Confirm": "Această operatiune va descărca toate pad-urile și fișierele din CryptDrive-ul tău. Dacă vrei să continui, alege un nume și apasă OK",
+ "settings_exportTitle": "Exportă-ți CryptDrive-ul",
+ "settings_exportDescription": "Te rugăm să aștepți descărcarea și decriptarea documentelor tale. Această operațiune poate dura câteva minute. Închiderea ferestrei va întrerupe acest proces.",
+ "settings_exportFailed": "Dacă descărcarea unui pad durează mai mult de 1 minut, acesta nu va fi inclus în export. Exportul va conține link-uri către toate pad-urile care nu au fost descărcate.",
+ "settings_exportWarning": "Notă: acest program este în versiune beta și ar putea avea probleme de scalabilitate. Pentru o performanță mai bună, îți recomandam să rămâi pe acest tab",
+ "settings_exportCancel": "Ești sigur că vrei să anulezi exportul? Va trebui să-l rulezi de la început data viitoare.",
+ "settings_export_reading": "Citire CryptDrive...",
+ "settings_export_download": "Descărcarea și decriptarea documentelor tale este în curs...",
+ "settings_export_compressing": "Comprimăm datele...",
+ "settings_export_done": "Descărcarea este gata!",
+ "settings_exportError": "Vizualizează erorile",
+ "settings_exportErrorDescription": "Următoarele documente nu au putut fi adăugate în export:",
+ "settings_exportErrorEmpty": "Acest document nu poate fi exportat (conținut gol sau invalid)",
+ "settings_exportErrorMissing": "Acest document lipsește de pe serverele noastre (este expirat sau a fost șters de către proprietarul său)",
+ "settings_exportErrorOther": "A apărut o eroare in timpul exportului acestui document: {0}",
+ "settings_resetNewTitle": "Curățare CryptDrive",
+ "settings_resetButton": "Șterge",
+ "settings_resetTipsAction": "Resetează",
+ "settings_thumbnails": "Miniaturi"
}
diff --git a/www/common/translations/messages.ru.json b/www/common/translations/messages.ru.json
index 3c43c4a8e..3e473505b 100644
--- a/www/common/translations/messages.ru.json
+++ b/www/common/translations/messages.ru.json
@@ -335,5 +335,22 @@
"contacts_info4": "Любой участник может полностью очистить историю чата",
"contacts_confirmRemoveHistory": "Вы уверены, что хотите навсегда удалить свою историю чата? Данные не могут быть восстановлены",
"contacts_removeHistoryServerError": "При удалении истории чата произошла ошибка. Попробуйте позже еще раз",
- "contacts_online": "Другой пользователь из этой комнаты находится онлайн"
+ "contacts_online": "Другой пользователь из этой комнаты находится онлайн",
+ "fm_newButtonTitle": "Создайте новый пэд или папку, импортируйте файл в текущую папку",
+ "fm_lastAccess": "Последний доступ",
+ "fm_creation": "Создание",
+ "fm_forbidden": "Запрещенное действие",
+ "fm_removePermanentlyDialog": "Вы уверены, что хотите навсегда удалить этот элемент из вашего хранилища?",
+ "fm_removeSeveralDialog": "Вы уверены, что хотите перенести эти {0} элементы в корзину?",
+ "fm_removeDialog": "Вы уверены, что хотите переместить {0} в корзину?",
+ "fm_deleteOwnedPad": "Вы уверены, что хотите навсегда удалить этот пэд с сервера?",
+ "fm_deleteOwnedPads": "Вы уверены, что хотите навсегда удалить эти пэды с сервера?",
+ "fm_restoreDialog": "Вы уверены, что хотите восстановить {0} в прежнее местоположение?",
+ "fm_unknownFolderError": "Выбранный или последний раз посещенный каталог больше не существует. Открывается изначальная папка...",
+ "fm_contextMenuError": "Невозможно открыть контекстное меню для данного элемента. Если проблема остается, попробуйте перезагрузить страницу.",
+ "fm_selectError": "Невозможно выбрать выбранный элемент. Если проблема сохраняется, попробуйте перезагрузить страницу.",
+ "fm_info_root": "Создавайте здесь неограниченное количество вложенных папок, чтобы сортировать ваши файлы.",
+ "fm_info_template": "Содержит все пэды, сохраненные в виде шаблонов, которые можно использовать повторно при создании нового пэда.",
+ "fm_info_recent": "Показать недавно измененные или открытые пэды.",
+ "fm_info_trash": "Опустошите корзину, чтобы освободить место в хранилище."
}
diff --git a/www/common/userObject.js b/www/common/userObject.js
index ce6a3168d..1a2229d40 100644
--- a/www/common/userObject.js
+++ b/www/common/userObject.js
@@ -95,6 +95,8 @@ define([
exp.isReadOnlyFile = function (element) {
if (!isFile(element)) { return false; }
var data = exp.getFileData(element);
+ // undefined means this pad doesn't support read-only
+ if (!data.roHref) { return; }
return Boolean(data.roHref && !data.href);
};
diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less
index 5bf6a7ea8..3554306e8 100644
--- a/www/drive/app-drive.less
+++ b/www/drive/app-drive.less
@@ -509,6 +509,11 @@
}
}
}
+ &.cp-app-drive-element-separator {
+ text-align: left;
+ font-weight: bold;
+ margin-top: 0;
+ }
}
}
.cp-app-drive-element {
@@ -521,6 +526,12 @@
&.cp-app-drive-element {
position: relative;
}
+ &.cp-app-drive-element-separator {
+ display: block;
+ height: auto;
+ width: auto;
+ border: none !important;
+ }
input {
width: 100%;
margin: 0;
@@ -600,6 +611,14 @@
height: @variables_bar-height;
line-height: @variables_bar-height;
}
+ &.cp-app-drive-element-separator {
+ position: relative;
+ height: 1.5 * @variables_bar-height;
+ line-height: 1.5 * @variables_bar-height;
+ span {
+ position: absolute;
+ }
+ }
&.cp-app-drive-element-header {
cursor: default;
color: @drive_table-header-fg;
diff --git a/www/drive/inner.js b/www/drive/inner.js
index 850db5450..f6146fb4e 100644
--- a/www/drive/inner.js
+++ b/www/drive/inner.js
@@ -2454,24 +2454,55 @@ define([
var displayRecent = function ($list) {
var filesList = manager.getRecentPads();
var limit = 20;
+
+ var now = new Date();
+ var last1 = new Date(now);
+ last1.setDate(last1.getDate()-1);
+ var last7 = new Date(now);
+ last7.setDate(last7.getDate()-7);
+ var last28 = new Date(now);
+ last28.setDate(last28.getDate()-28);
+
+ var header7, header28, headerOld;
var i = 0;
- filesList.forEach(function (id) {
- if (i >= limit) { return; }
- // Check path (pad exists and not in trash)
+ var channels = [];
+
+ $list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active1Day)));
+ filesList.some(function (arr) {
+ if (i >= limit) { return true; }
+ var id = arr[0];
+ var file = arr[1];
+ if (!file || !file.atime) { return; }
+
+ if (file.atime <= last28 && i >= limit) {
+ return true;
+ }
+
var paths = manager.findFile(id);
if (!paths.length) { return; }
var path = paths[0];
if (manager.isPathIn(path, [TRASH])) { return; }
- // Display the pad
- var file = manager.getFileData(id);
- if (!file) {
- //debug("Unsorted or template returns an element not present in filesData: ", href);
- file = { title: Messages.fm_noname };
- //return;
+
+ if (channels.indexOf(file.channel) !== -1) { return; }
+ channels.push(file.channel);
+
+ if (!header7 && file.atime < last1) {
+ $list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active7Days)));
+ header7 = true;
}
+ if (!header28 && file.atime < last7) {
+ $list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active28Days)));
+ header28 = true;
+ }
+ if (!headerOld && file.atime < last28) {
+ $list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_activeOld)));
+ headerOld = true;
+ }
+
+ // Display the pad
var $icon = getFileIcon(id);
var ro = manager.isReadOnlyFile(id);
- // ro undefined mens it's an old hash which doesn't support read-only
+ // ro undefined means it's an old hash which doesn't support read-only
var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' :
ro ? ' cp-app-drive-element-readonly' : '';
var $element = $('', {
diff --git a/www/drive/tests.js b/www/drive/tests.js
index 015178208..1633740bc 100644
--- a/www/drive/tests.js
+++ b/www/drive/tests.js
@@ -268,9 +268,21 @@ define([
assert(function (cb) {
console.log('START DRIVE utils');
var files = JSON.parse(JSON.stringify(example));
+
+ var href6 = "/pad/#67a9385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyGbj7LNy/";
+ var id6 = 1000000000006;
+ var data = {
+ href: href6,
+ title: 'Title6',
+ atime: +new Date(),
+ ctime: +new Date()
+ };
+ files.filesData[id6] = data;
+
var fo = FO.init(files, config);
fo.fixFiles();
+
if (fo.isFile({}) || fo.isFile(href1) || !fo.isFile(href1, true) || !fo.isFile(id1)) {
console.log("DRIVE utils: isFile returns an incorrect value");
return cb();
@@ -283,6 +295,10 @@ define([
console.log("DRIVE utils: isReadOnlyFile returns false for a 'view' file");
return cb();
}
+ if (typeof fo.isReadOnlyFile(id6) !== "undefined") {
+ console.log("DRIVE utils: isReadOnlyFile should return undefined for a v0 hash");
+ return cb();
+ }
if (!fo.hasSubfolder(files.root.Folder) || fo.hasSubfolder(files.root.Folder2)) {
console.log("DRIVE utils: hasSubfolder returns an incorrect value");
return cb();
@@ -303,7 +319,7 @@ define([
console.log("DRIVE utils: 'find' returns an incorrect value");
return cb();
}
- if (fo.getFiles().length !== 4 || fo.getFiles(['trash']).length !== 2) {
+ if (fo.getFiles().length !== 5 || fo.getFiles(['trash']).length !== 2) {
console.log("DRIVE utils: getFiles returns an incorrect value");
return cb();
}
diff --git a/www/settings/inner.js b/www/settings/inner.js
index e74495c82..85934c765 100644
--- a/www/settings/inner.js
+++ b/www/settings/inner.js
@@ -832,9 +832,9 @@ define([
var localStore = window.cryptpadStore;
$button.click(function () {
- Object.keys(localStore).forEach(function (k) {
+ Object.keys(localStore.store).forEach(function (k) {
if(k.slice(0, 9) === "hide-info") {
- localStore.put(k, undefined);
+ localStore.put(k, null);
}
});
UI.alert(Messages.settings_resetTipsDone);