Merge branch 'staging' into editable-metadata

pull/1/head
ansuz 5 years ago
commit 1c55ac15d5

@ -7,6 +7,7 @@ define(function () {
fileHashKey: 'FS_hash',
// sessionStorage
newPadPathKey: "newPadPath",
newPadFileData: "newPadFileData",
// Store
displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS',

@ -15,6 +15,7 @@ define([
};
var supportedTypes = [
'text/plain',
'image/png',
'image/jpeg',
'image/jpg',
@ -23,7 +24,12 @@ define([
'application/pdf'
];
Thumb.isSupportedType = function (type) {
Thumb.isSupportedType = function (file) {
if (!file) { return false; }
var type = file.type;
if (Util.isPlainTextFile(file.type, file.name)) {
type = "text/plain";
}
return supportedTypes.some(function (t) {
return type.indexOf(t) !== -1;
});
@ -164,6 +170,26 @@ define([
});
});
};
Thumb.fromPlainTextBlob = function (blob, cb) {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = Thumb.dimension;
var reader = new FileReader();
reader.addEventListener('loadend', function (e) {
var content = e.srcElement.result;
var lines = content.split("\n");
var canvasContext = canvas.getContext("2d");
var fontSize = 4;
canvas.height = (lines.length) * (fontSize + 1);
canvasContext.font = fontSize + 'px monospace';
lines.forEach(function (text, i) {
canvasContext.fillText(text, 5, i * (fontSize + 1));
});
var D = getResizedDimensions(canvas, "txt");
Thumb.fromCanvas(canvas, D, cb);
});
reader.readAsText(blob);
};
Thumb.fromBlob = function (blob, cb) {
if (blob.type.indexOf('video/') !== -1) {
return void Thumb.fromVideoBlob(blob, cb);
@ -171,6 +197,9 @@ define([
if (blob.type.indexOf('application/pdf') !== -1) {
return void Thumb.fromPdfBlob(blob, cb);
}
if (Util.isPlainTextFile(blob.type, blob.name)) {
return void Thumb.fromPlainTextBlob(blob, cb);
}
Thumb.fromImageBlob(blob, cb);
};

@ -2301,7 +2301,10 @@ define([
if (!common.isLoggedIn()) { return void cb(); }
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var type = metadataMgr.getMetadataLazy().type;
var fromFileData = privateData.fromFileData;
var $body = $('body');
var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
@ -2313,7 +2316,8 @@ define([
// Title
//var colorClass = 'cp-icon-color-'+type;
//$creation.append(h('h2.cp-creation-title', Messages.newButtonTitle));
$creation.append(h('h3.cp-creation-title', Messages['button_new'+type]));
var newPadH3Title = Messages['button_new' + type];
$creation.append(h('h3.cp-creation-title', newPadH3Title));
//$creation.append(h('h2.cp-creation-title.'+colorClass, Messages.newButtonTitle));
// Deleted pad warning
@ -2323,7 +2327,7 @@ define([
));
}
var origin = common.getMetadataMgr().getPrivateData().origin;
var origin = privateData.origin;
var createHelper = function (href, text) {
var q = h('a.cp-creation-help.fa.fa-question-circle', {
title: text,
@ -2480,7 +2484,26 @@ define([
});
if (i < TEMPLATES_DISPLAYED) { $(left).addClass('hidden'); }
};
redraw(0);
if (fromFileData) {
var todo = function (thumbnail) {
allData = [{
name: fromFileData.title,
id: 0,
thumbnail: thumbnail,
icon: h('span.cptools.cptools-file'),
}];
redraw(0);
};
todo();
sframeChan.query("Q_GET_FILE_THUMBNAIL", null, function (err, res) {
if (err || (res && res.error)) { return; }
todo(res.data);
});
}
else {
redraw(0);
}
// Change template selection when Tab is pressed
next = function (revert) {

@ -325,6 +325,30 @@ define([], function () {
return div.innerText;
};
// return an object containing {name, ext}
// or {} if the name could not be parsed
Util.parseFilename = function (filename) {
if (!filename || !filename.trim()) { return {}; }
var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(filename) || [];
return {
name: parsedName[1],
ext: parsedName[2],
};
};
// Tell if a file is plain text from its metadata={title, fileType}
Util.isPlainTextFile = function (type, name) {
// does its type begins with "text/"
if (type && type.indexOf("text/") === 0) { return true; }
// no type and no file extension -> let's guess it's plain text
var parsedName = Util.parseFilename(name);
if (!type && name && !parsedName.ext) { return true; }
// other exceptions
if (type === 'application/x-javascript') { return true; }
if (type === 'application/xml') { return true; }
return false;
};
return Util;
});
}(self));

@ -21,6 +21,11 @@ define([
S.leave();
} catch (e) { console.log(e); }
}
if (S.realtime && S.realtime.stop) {
try {
S.realtime.stop();
} catch (e) { console.error(e); }
}
var abort = Util.find(S, ['session', 'realtime', 'abort']);
if (typeof(abort) === 'function') {
S.session.realtime.sync();
@ -52,11 +57,12 @@ define([
Object.keys(b).forEach(function (k) { a[k] = b[k]; });
};
var get = function (hash, cb, opt) {
var get = function (hash, cb, opt, progress) {
if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback');
}
opt = opt || {};
progress = progress || function () {};
var config = makeConfig(hash, opt);
var Session = { cb: cb, hasNetwork: Boolean(opt.network) };
@ -65,6 +71,7 @@ define([
var rt = Session.session = info.realtime;
Session.network = info.network;
Session.leave = info.leave;
progress(1);
finish(Session, void 0, rt.getUserDoc());
};
@ -72,6 +79,16 @@ define([
finish(Session, info.error);
};
// We use the new onMessage handler to compute the progress:
// we should receive 2 checkpoints max, so 100 messages max
// We're going to consider that 1 message = 1%, and we'll send 100%
// at the end
var i = 0;
config.onMessage = function () {
i++;
progress(Math.min(0.99, i/100));
};
overwrite(config, opt);
Session.realtime = CPNetflux.start(config);

@ -571,6 +571,66 @@ define([
});
};
common.useFile = function (Crypt, cb, optsPut) {
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 = Hash.getBlobPathFromHex(secret.channel);
var key = secret.keys && secret.keys.cryptKey;
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";
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(function () {
Crypt.put(parsed2.hash, JSON.stringify(val), cb, optsPut);
});
};
// Forget button
common.moveToTrash = function (cb, href) {
href = href || window.location.href;
@ -1274,6 +1334,12 @@ define([
messenger: rdyCfg.messenger, // Boolean
driveEvents: rdyCfg.driveEvents // Boolean
};
// if a pad is created from a file
if (sessionStorage[Constants.newPadFileData]) {
common.fromFileData = JSON.parse(sessionStorage[Constants.newPadFileData]);
delete sessionStorage[Constants.newPadFileData];
}
if (sessionStorage[Constants.newPadPathKey]) {
common.initialPath = sessionStorage[Constants.newPadPathKey];
delete sessionStorage[Constants.newPadPathKey];

@ -1,11 +1,13 @@
define([
'/common/cryptget.js',
'/file/file-crypto.js',
'/common/common-hash.js',
'/common/sframe-common-file.js',
'/common/common-util.js',
'/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js',
'/bower_components/jszip/dist/jszip.min.js',
], function (Crypt, Hash, SFCFile, nThen, Saferphore, JsZip) {
], function (Crypt, FileCrypto, Hash, Util, nThen, Saferphore, JsZip) {
var saveAs = window.saveAs;
var sanitize = function (str) {
return str.replace(/[\\/?%*:|"<>]/gi, '_')/*.toLowerCase()*/;
@ -43,6 +45,88 @@ define([
});
};
var _downloadFile = function (ctx, fData, cb, updateProgress) {
var cancelled = false;
var cancel = function () {
cancelled = true;
};
var parsed = Hash.parsePadUrl(fData.href || fData.roHref);
var hash = parsed.hash;
var name = fData.filename || fData.title;
var secret = Hash.getSecrets('file', hash, fData.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = secret.keys && secret.keys.cryptKey;
Util.fetch(src, function (err, u8) {
if (cancelled) { return; }
if (err) { return void cb('E404'); }
FileCrypto.decrypt(u8, key, function (err, res) {
if (cancelled) { return; }
if (err) { return void cb(err); }
if (!res.content) { return void cb('EEMPTY'); }
var dl = function () {
saveAs(res.content, name || res.metadata.name);
};
cb(null, {
metadata: res.metadata,
content: res.content,
download: dl
});
}, updateProgress && updateProgress.progress2);
}, updateProgress && updateProgress.progress);
return {
cancel: cancel
};
};
var _downloadPad = function (ctx, pData, cb, updateProgress) {
var cancelled = false;
var cancel = function () {
cancelled = true;
};
var parsed = Hash.parsePadUrl(pData.href || pData.roHref);
var name = pData.filename || pData.title;
var opts = {
password: pData.password
};
var done = false;
ctx.sframeChan.on("EV_CRYPTGET_PROGRESS", function (data) {
if (done || data.hash !== parsed.hash) { return; }
updateProgress.progress(data.progress);
if (data.progress === 1) {
done = true;
updateProgress.progress2(1);
}
});
ctx.get({
hash: parsed.hash,
opts: opts
}, function (err, val) {
if (cancelled) { return; }
if (err) { return; }
if (!val) { return; }
transform(ctx, parsed.type, val, function (res) {
if (cancelled) { return; }
if (!res.data) { return; }
var dl = function () {
saveAs(res.data, Util.fixFileName(name));
};
cb(null, {
metadata: res.metadata,
content: res.data,
download: dl
});
});
});
return {
cancel: cancel
};
};
// Add a file to the zip. We have to cryptget&transform it if it's a pad
// or fetch&decrypt it if it's a file.
var addFile = function (ctx, zip, fData, existingNames) {
@ -126,7 +210,7 @@ define([
// Files (mediatags...)
var todoFile = function () {
var it;
var dl = SFCFile.downloadFile(fData, function (err, res) {
var dl = _downloadFile(ctx, fData, function (err, res) {
if (it) { clearInterval(it); }
if (err) { return void error(err); }
var opts = {
@ -189,6 +273,7 @@ define([
var ctx = {
get: getPad,
data: data.uo.drive,
folder: data.folder || ctx.data.root,
sf: data.sf,
zip: new JsZip(),
errors: [],
@ -197,11 +282,12 @@ define([
max: 0,
done: 0
};
var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData;
progress('reading', -1);
nThen(function (waitFor) {
ctx.waitFor = waitFor;
var zipRoot = ctx.zip.folder('Root');
makeFolder(ctx, ctx.data.root, zipRoot, ctx.data.filesData);
makeFolder(ctx, ctx.folder, zipRoot, filesData);
progress('download', {});
}).nThen(function () {
console.log(ctx.zip);
@ -222,7 +308,33 @@ define([
};
};
var _downloadFolder = function (ctx, data, cb, updateProgress) {
create(data, ctx.get, function (blob, errors) {
console.error(errors); // TODO show user errors
var dl = function () {
saveAs(blob, data.folderName);
};
cb(null, {download: dl});
}, function (state, progress) {
if (state === "reading") {
updateProgress.folderProgress(0);
}
if (state === "download") {
if (typeof progress.current !== "number") { return; }
updateProgress.folderProgress(progress.current / progress.max);
}
else if (state === "done") {
updateProgress.folderProgress(1);
}
});
};
return {
create: create
create: create,
downloadFile: _downloadFile,
downloadPad: _downloadPad,
downloadFolder: _downloadFolder,
};
});

@ -1108,7 +1108,6 @@ define([
var channels = Store.channels = store.channels = {};
Store.joinPad = function (clientId, data) {
console.log('joining', data.channel);
var isNew = typeof channels[data.channel] === "undefined";
var channel = channels[data.channel] = channels[data.channel] || {
queue: [],
@ -1189,9 +1188,6 @@ define([
onLeave: function (m) {
channel.bcast("PAD_LEAVE", m);
},
onAbort: function () {
channel.bcast("PAD_DISCONNECT");
},
onError: function (err) {
channel.bcast("PAD_ERROR", err);
delete channels[data.channel];
@ -1200,7 +1196,11 @@ define([
channel.bcast("PAD_ERROR", err);
delete channels[data.channel];
},
onConnectionChange: function () {},
onConnectionChange: function (info) {
if (!info.state) {
channel.bcast("PAD_DISCONNECT");
}
},
crypto: {
// The encryption and decryption is done in the outer window.
// This async-store only deals with already encrypted messages.

@ -1,6 +1,7 @@
define([
'jquery',
'/file/file-crypto.js',
'/common/make-backup.js',
'/common/common-thumbnail.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
@ -11,9 +12,8 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto, Thumb, UI, UIElements, Util, Hash, h, Messages) {
], function ($, FileCrypto, MakeBackup, Thumb, UI, UIElements, Util, Hash, h, Messages) {
var Nacl = window.nacl;
var saveAs = window.saveAs;
var module = {};
var blobToArrayBuffer = function (blob, cb) {
@ -367,7 +367,7 @@ define([
blobToArrayBuffer(file, function (e, buffer) {
if (e) { console.error(e); }
file_arraybuffer = buffer;
if (!Thumb.isSupportedType(file.type)) { return getName(); }
if (!Thumb.isSupportedType(file)) { return getName(); }
// make a resized thumbnail from the image..
Thumb.fromBlob(file, function (e, thumb64) {
if (e) { console.error(e); }
@ -446,124 +446,108 @@ define([
createUploader(config.dropArea, config.hoverArea, config.body);
File.downloadFile = function (fData, cb) {
var parsed = Hash.parsePadUrl(fData.href || fData.roHref);
var hash = parsed.hash;
var name = fData.filename || fData.title;
var secret = Hash.getSecrets('file', hash, fData.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = secret.keys && secret.keys.cryptKey;
common.getFileSize(secret.channel, function (e, data) {
var todo = function (file) {
if (queue.inProgress) { return; }
queue.inProgress = true;
var id = file.id;
var $row = $table.find('tr[id="'+id+'"]');
var $pv = $row.find('.cp-fileupload-table-progress-value');
var $pb = $row.find('.cp-fileupload-table-progress-container');
var $pc = $row.find('.cp-fileupload-table-progress');
var $link = $row.find('.cp-fileupload-table-link');
var done = function () {
$row.find('.cp-fileupload-table-cancel').text('-');
queue.inProgress = false;
queue.next();
};
var updateProgressbar = function (file, data, downloadFunction, cb) {
if (queue.inProgress) { return; }
queue.inProgress = true;
var id = file.id;
var updateDLProgress = function (progressValue) {
var text = Math.round(progressValue*100) + '%';
text += ' ('+ Messages.download_step1 +'...)';
$pv.text(text);
$pb.css({
width: progressValue * $pc.width()+'px'
});
};
var updateProgress = function (progressValue) {
var text = Math.round(progressValue*100) + '%';
text += progressValue === 1 ? '' : ' ('+ Messages.download_step2 +'...)';
$pv.text(text);
$pb.css({
width: progressValue * $pc.width()+'px'
});
};
var $row = $table.find('tr[id="'+id+'"]');
var $pv = $row.find('.cp-fileupload-table-progress-value');
var $pb = $row.find('.cp-fileupload-table-progress-container');
var $pc = $row.find('.cp-fileupload-table-progress');
var $link = $row.find('.cp-fileupload-table-link');
var dl = module.downloadFile(fData, function (err, obj) {
$link.prepend($('<span>', {'class': 'fa fa-external-link'}))
.attr('href', '#')
.click(function (e) {
e.preventDefault();
obj.download();
});
done();
if (obj) { obj.download(); }
cb(err, obj);
}, {
src: src,
key: key,
name: name,
progress: updateDLProgress,
progress2: updateProgress,
});
var done = function () {
$row.find('.cp-fileupload-table-cancel').text('-');
queue.inProgress = false;
queue.next();
};
var $cancel = $('<span>', {'class': 'cp-fileupload-table-cancel-button fa fa-times'}).click(function () {
dl.cancel();
$cancel.remove();
$row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled);
done();
});
$row.find('.cp-fileupload-table-cancel').html('').append($cancel);
};
var updateDLProgress = function (progressValue) {
var text = Math.round(progressValue * 100) + '%';
text += ' (' + Messages.download_step1 + '...)';
$pv.text(text);
$pb.css({
width: progressValue * $pc.width() + 'px'
});
};
var updateDecryptProgress = function (progressValue) {
var text = Math.round(progressValue*100) + '%';
text += progressValue === 1 ? '' : ' (' + Messages.download_step2 + '...)';
$pv.text(text);
$pb.css({
width: progressValue * $pc.width()+'px'
});
};
var updateProgress = function (progressValue) {
var text = Math.round(progressValue*100) + '%';
$pv.text(text);
$pb.css({
width: progressValue * $pc.width()+'px'
});
};
var ctx = {
get: common.getPad,
sframeChan: sframeChan,
};
downloadFunction(ctx, data, function (err, obj) {
$link.prepend($('<span>', {'class': 'fa fa-external-link'}))
.attr('href', '#')
.click(function (e) {
e.preventDefault();
obj.download();
});
done();
if (obj) { obj.download(); }
cb(err, obj);
}, {
progress: updateDLProgress,
progress2: updateDecryptProgress,
folderProgress: updateProgress,
});
// var $cancel = $('<span>', {'class': 'cp-fileupload-table-cancel-button fa fa-times'}).click(function () {
// dl.cancel();
// $cancel.remove();
// $row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled);
// done();
// });
// $row.find('.cp-fileupload-table-cancel').html('').append($cancel);
$row.find('.cp-fileupload-table-cancel').html('');
};
File.downloadFile = function (fData, cb) {
var name = fData.filename || fData.title;
common.getFileSize(fData.channel, function (e, data) {
queue.push({
dl: todo,
dl: function (file) { updateProgressbar(file, fData, MakeBackup.downloadFile, cb); },
size: data,
name: name
});
});
};
return File;
};
module.downloadFile = function (fData, cb, obj) {
var cancelled = false;
var cancel = function () {
cancelled = true;
File.downloadPad = function (pData, cb) {
queue.push({
dl: function (file) { updateProgressbar(file, pData, MakeBackup.downloadPad, cb); },
size: 0,
name: pData.title,
});
};
var src, key, name;
if (obj && obj.src && obj.key && obj.name) {
src = obj.src;
key = obj.key;
name = obj.name;
} else {
var parsed = Hash.parsePadUrl(fData.href || fData.roHref);
var hash = parsed.hash;
name = fData.filename || fData.title;
var secret = Hash.getSecrets('file', hash, fData.password);
src = Hash.getBlobPathFromHex(secret.channel);
key = secret.keys && secret.keys.cryptKey;
}
Util.fetch(src, function (err, u8) {
if (cancelled) { return; }
if (err) { return void cb('E404'); }
FileCrypto.decrypt(u8, key, function (err, res) {
if (cancelled) { return; }
if (err) { return void cb(err); }
if (!res.content) { return void cb('EEMPTY'); }
var dl = function () {
saveAs(res.content, name || res.metadata.name);
};
cb(null, {
metadata: res.metadata,
content: res.content,
download: dl
});
}, obj && obj.progress2);
}, obj && obj.progress);
return {
cancel: cancel
File.downloadFolder = function (data, cb) {
queue.push({
dl: function (file) { updateProgressbar(file, data, MakeBackup.downloadFolder, cb); },
size: 0,
name: data.folderName,
});
};
return File;
};
return module;
});

@ -319,6 +319,9 @@ define([
channel: secret.channel,
enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default
devMode: localStorage.CryptPad_dev === "1",
fromFileData: Cryptpad.fromFileData ? {
title: Cryptpad.fromFileData.title
} : undefined,
};
if (window.CryptPad_newSharedFolder) {
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
@ -359,6 +362,8 @@ define([
sframeChan.event("EV_NEW_VERSION");
});
// Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
@ -811,6 +816,22 @@ define([
});
});
sframeChan.on('Q_GET_FILE_THUMBNAIL', function (data, cb) {
if (!Cryptpad.fromFileData || !Cryptpad.fromFileData.href) {
return void cb({
error: "EINVAL",
});
}
var key = getKey(Cryptpad.fromFileData.href, Cryptpad.fromFileData.channel);
Utils.LocalStore.getThumbnail(key, function (e, data) {
if (data === "EMPTY") { data = null; }
cb({
error: e,
data: data
});
});
});
sframeChan.on('EV_GOTO_URL', function (url) {
if (url) {
window.location.href = url;
@ -864,6 +885,9 @@ define([
Cryptpad.removeLoginBlock(data, cb);
});
// It seems we have performance issues when we open and close a lot of channels over
// the same network, maybe a memory leak. To fix this, we kill and create a new
// network every 30 cryptget calls (1 call = 1 channel)
var cgNetwork;
var whenCGReady = function (cb) {
if (cgNetwork && cgNetwork !== true) { console.log(cgNetwork); return void cb(); }
@ -880,7 +904,12 @@ define([
error: err,
data: val
});
}, data.opts);
}, data.opts, function (progress) {
sframeChan.event("EV_CRYPTGET_PROGRESS", {
hash: data.hash,
progress: progress,
});
});
};
//return void todo();
if (i > 30) {
@ -1089,11 +1118,11 @@ define([
}));
}
}).nThen(function () {
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
if (data.template) {
// Pass rtConfig to useTemplate because Cryptput will create the file and
// we need to have the owners and expiration time in the first line on the
// server
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
Cryptpad.useTemplate({
href: data.template
}, Cryptget, function () {
@ -1102,6 +1131,14 @@ define([
}, cryptputCfg);
return;
}
// if we open a new code from a file
if (Cryptpad.fromFileData) {
Cryptpad.useFile(Cryptget, function () {
startRealtime();
cb();
}, cryptputCfg);
return;
}
// Start realtime outside the iframe and callback
startRealtime(rtConfig);
cb();

@ -459,6 +459,14 @@ define([
});
}; */
funcs.getPad = function (data, cb) {
ctx.sframeChan.query("Q_CRYPTGET", data, function (err, obj) {
if (err) { return void cb(err); }
if (obj.error) { return void cb(obj.error); }
cb(null, obj.data);
}, { timeout: 60000 });
};
funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); };
funcs.openURL = function (url) { ctx.sframeChan.event('EV_OPEN_URL', url); };
funcs.openUnsafeURL = function (url) {

@ -575,7 +575,6 @@ MessengerUI, Messages) {
};
var createRequest = function (toolbar, config) {
console.error('test');
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the request access button");
}

@ -1112,16 +1112,17 @@
"notifications_cat_pads": "Mit mir geteilt",
"notifications_cat_archived": "Verlauf",
"notifications_dismissAll": "Alle verbergen",
"support_notification": "",
"requestEdit_button": "",
"requestEdit_dialog": "",
"requestEdit_confirm": "",
"requestEdit_fromFriend": "",
"requestEdit_fromStranger": "",
"requestEdit_viewPad": "",
"later": "",
"requestEdit_request": "",
"requestEdit_accepted": "",
"requestEdit_sent": "",
"uploadFolderButton": ""
"support_notification": "Ein Administrator hat dein Support-Ticket beantwortet",
"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",
"requestEdit_accepted": "{1} hat dir Bearbeitungsrechte für das Pad <b>{0}</b> gegeben",
"requestEdit_sent": "Anfrage gesendet",
"uploadFolderButton": "Ordner hochladen",
"properties_unknownUser": "{0} unbekannte(r) Benutzer"
}

@ -1111,5 +1111,18 @@
"admin_supportInitHint": "Vous pouvez configurer une messagerie de support afin de fournir aux utilisateurs de votre instance CryptPad un moyen de vous contacter de manière sécurisée en cas de problème avec leur compte.",
"admin_supportListHint": "Voici la liste des tickets envoyés par les utilisateurs au support. Tous les administrateurs peuvent voir les tickets et leurs réponses. Un ticket fermé ne peut pas être ré-ouvert. Vous ne pouvez supprimer (ou cacher) que les tickets fermés, et les tickets supprimés restent visible par les autres administrateurs.",
"support_formHint": "Ce formulaire peut être utilisé pour créer un nouveau ticket de support. Utilisez-le pour contacter les administrateurs de manière sécurisée afin de résoudre un problème ou d'obtenir des renseignements. Merci de ne pas créer de nouveau ticket si vous avez déjà un ticket ouvert concernant le même problème, vous pouvez utiliser le bouton \"Répondre\" dans ce cas.",
"support_listHint": "Voici la liste des tickets envoyés au support, ainsi que les réponses. Un ticket fermé ne peut pas être ré-ouvert, mais il est possible d'en créer un nouveau. Vous pouvez cacher les tickets qui ont été fermés."
"support_listHint": "Voici la liste des tickets envoyés au support, ainsi que les réponses. Un ticket fermé ne peut pas être ré-ouvert, mais il est possible d'en créer un nouveau. Vous pouvez cacher les tickets qui ont été fermés.",
"support_notification": "Un administrateur a répondu à votre ticket de support",
"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>{1}</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>",
"requestEdit_accepted": "{1} vous a accordé les droits d'édition du pad <b>{0}</b>",
"requestEdit_sent": "Demande envoyée",
"uploadFolderButton": "Importer un dossier",
"properties_unknownUser": "{0} utilisateur(s) inconnu(s)"
}

@ -321,6 +321,7 @@
"fm_newButtonTitle": "Create a new pad or folder, import a file in the current folder",
"fm_newFolder": "New folder",
"fm_newFile": "New pad",
"fm_morePads": "More pads",
"fm_folder": "Folder",
"fm_sharedFolder": "Shared folder",
"fm_folderName": "Folder name",
@ -383,6 +384,7 @@
"fc_color": "Change color",
"fc_open": "Open",
"fc_open_ro": "Open (read-only)",
"fc_openInCode": "Open in Code editor",
"fc_expandAll": "Expand All",
"fc_collapseAll": "Collapse All",
"fc_delete": "Move to trash",
@ -554,6 +556,10 @@
"upload_modal_title": "File upload options",
"upload_modal_filename": "File name (extension <em>{0}</em> added automatically)",
"upload_modal_owner": "Owned file",
"uploadFolder_modal_title": "Folder upload options",
"uploadFolder_modal_filesPassword": "Files password",
"uploadFolder_modal_owner": "Owned files",
"uploadFolder_modal_forceSave": "Store files in your CryptDrive",
"upload_serverError": "Server Error: unable to upload your file at this time.",
"upload_uploadPending": "You already have an upload in progress. Cancel it and upload your new file?",
"upload_success": "Your file ({0}) has been successfully uploaded and added to your drive.",
@ -989,6 +995,9 @@
"sharedFolders_create_owned": "Owned folder",
"sharedFolders_create_password": "Folder password",
"sharedFolders_share": "Share this URL with other registered users to give them access to the shared folder. Once they open this URL, the shared folder will be added to the root directory of their CryptDrive.",
"convertFolderToSF_SFParent": "This folder cannot be converted to a shared folder in its current location. Move it outside of the containing shared folder to continue.",
"convertFolderToSF_SFChildren": "This folder cannot be converted to a shared folder because it already contains shared folders. Move those Shared folders elsewhere to continue.",
"convertFolderToSF_confirm": "This folder must be converted to a Shared folder for others to view it. Continue?",
"chrome68": "It seems that you're using the browser Chrome or Chromium version 68. It contains a bug resulting in the page turning completely white after a few seconds or the page being unresponsive to clicks. To fix this issue, you can switch to another tab and come back, or try to scroll in the page. This bug should be fixed in the next version of your browser.",
"autostore_file": "file",
"autostore_sf": "folder",

@ -8,7 +8,11 @@
"drive": "Drive",
"whiteboard": "Whiteboard",
"file": "File",
"media": "Media"
"media": "Media",
"kanban": "Kanban",
"todo": "A Fazer",
"contacts": "Contactos",
"sheet": "SpreadSheet (Beta)"
},
"button_newpad": "Novo bloco RTF",
"button_newcode": "Novo bloco de código",

@ -102,7 +102,7 @@
"forgetPrompt": "Нажав ОК, вы удалите документ в корзину. Уверены?",
"movedToTrash": "Документ был удалён в корзину.<br><a href=\"/drive/\">Доступ к диску</a>",
"shareButton": "Поделиться",
"shareSuccess": "Ссылка скопирована в буфер обмена.",
"shareSuccess": "Ссылка скопирована в буфер обмена",
"userListButton": "Список пользователей",
"chatButton": "Чат",
"userAccountButton": "Ваш профиль",
@ -139,36 +139,36 @@
"or": "или",
"tags_title": "Теги (только для вас)",
"tags_add": "Обновить теги страницы",
"tags_searchHint": "Начните поиск в вашем CryptDrive при помощи # чтобы найти пэды с тегами",
"tags_searchHint": "Начните поиск в вашем CryptDrive при помощи # чтобы найти пэды с тегами.",
"tags_notShared": "Ваши теги не разделяются с другими пользователями",
"button_newsheet": "Новый Лист",
"newButtonTitle": "Создать новый блокнот",
"newButtonTitle": "Создать новый документ",
"useTemplateCancel": "Начать заново (Esc)",
"previewButtonTitle": "Отобразить или скрыть режим предпросмотра разметки",
"printOptions": "Опции расположения",
"previewButtonTitle": "Показать или скрыть просмотр Маркдаун разметки",
"printOptions": "Настройки размещения",
"printBackgroundValue": "<b>Текущий фон:</b> <em>{0}</em>",
"printBackgroundNoValue": "<em>Нет отображаемого фонового изображения</em>",
"tags_duplicate": "Скопировать тег: {0}",
"tags_noentry": "Вы не можете присвоить тег удалённому блокноту!",
"slideOptionsText": "Опции",
"slideOptionsTitle": "Настроить ваши слайды",
"slideOptionsButton": "Сохранить (Enter)",
"slide_invalidLess": "Неверный настраиваемый стиль",
"printBackgroundNoValue": "<em>Фоновое изображение не показано</em>",
"tags_duplicate": "Скопировать метку: {0}",
"tags_noentry": "Вы не можете присвоить метку удалённому документу!",
"slideOptionsText": "Настройки",
"slideOptionsTitle": "Настройте ваши слайды",
"slideOptionsButton": "Сохранить (Ввод)",
"slide_invalidLess": "Неверный пользовательский стиль",
"languageButton": "Язык",
"languageButtonTitle": "Выберите язык, используемый для подсветки синтаксиса",
"languageButtonTitle": "Выберите язык для использования подсветки слов",
"themeButton": "Тема",
"themeButtonTitle": "Выберите цветовую тему, используемую в редакторе кода и слайдов",
"editShare": "Редактирование ссылки",
"editShareTitle": "Скопировать редактируемую ссылку",
"themeButtonTitle": "Выберите цветовую тему для использования в редакторе кода и слайдов",
"editShare": "Редактируемая ссылка",
"editShareTitle": "Скопировать редактируемую ссылку в буфер обмена",
"editOpen": "Открыть редактируемую ссылку в новой вкладке",
"editOpenTitle": "Открыть блокнот в режиме редактирования в новой вкладке",
"editOpenTitle": "Открыть данный документ для редактирования в новой вкладке",
"viewShare": "Ссылка только для чтения",
"viewShareTitle": "Скопировать ссылку для чтения в буфер обмена",
"viewOpen": "Открыть ссылку в режиме чтения в новой вкладке",
"viewOpenTitle": "Открыть блокнот в режиме чтения в новой вкладке",
"viewShareTitle": "Скопировать ссылку только для чтения в буфер обмена",
"viewOpen": "Открыть ссылку только для чтения в новой вкладке",
"viewOpenTitle": "Открыть данный документ для чтения в новой вкладке",
"fileShare": "Скопировать ссылку",
"getEmbedCode": "Получить код для встраивания",
"viewEmbedTitle": "Встроить блокнот на внешнюю страницу",
"viewEmbedTitle": "Встроить документ во внешнюю страницу",
"notifyJoined": "{0} присоединился к совместной сессии",
"notifyRenamed": "{0} теперь известен как {1}",
"notifyLeft": "{0} покинул совместную сессию",
@ -258,7 +258,7 @@
"profile_fieldSaved": "Сохранено новое значение: {0}",
"profile_viewMyProfile": "Посмотреть мой профиль",
"contacts_title": "Контакты",
"contacts_added": "Приглашение принято контактом",
"contacts_added": "Приглашение принято контактом.",
"contacts_rejected": "Контакт не принял приглашение",
"contacts_send": "Отправить",
"contacts_remove": "Убрать этот контакт",
@ -302,22 +302,22 @@
"crowdfunding_popup_no": "Не сейчас",
"crowdfunding_popup_never": "Не спрашивать меня снова",
"markdown_toc": "Содержимое",
"fm_expirablePad": "Этот блокнот удалится через {0}",
"fileEmbedTitle": "Вставить файл во внешнюю страницу",
"fm_expirablePad": "Этот блокнот истечет {0}",
"fileEmbedTitle": "Встроить файл во внешнюю страницу",
"kanban_removeItemConfirm": "Вы уверенны, что хотите удалить этот пункт?",
"settings_backup2": "Скачать мой CryptDrive",
"settings_backup2Confirm": "Это позволит скачать все пэды и файлы с вашего CryptDrive. Если вы хотите продолжить, выберите имя и нажмите OK",
"settings_exportTitle": "Экспортировать Ваш CryptDrive",
"fileEmbedScript": "Чтобы вставить этот файл, включите этот скрипт один раз на своей странице, чтобы загрузить медиатег:",
"fileEmbedTag": "Затем поместите медиатег в любое место на странице, куда вы хотите его вставить:",
"fileEmbedTag": "Затем поместите медиатег в любое место на странице,в которое вы хотите его вставить:",
"pad_mediatagRatio": "Оставить соотношение",
"kanban_item": "Элемент {0}",
"poll_p_encryption": "Все ваши данные зашифрованы, доступ к ним имеют только пользователи, имеющие доступ к этой ссылке. Даже сервер не видит, что вы меняете.",
"wizardLog": "Нажмите кнопку в левом верхнем углу, чтобы вернуться к опросу",
"poll_bookmark_col": "Добавить этот столбец в закладку, чтобы он всегда был разблокирован и отображался для вас в начале",
"poll_bookmarked_col": "Это твоя колонка закладок. Она всегда будет разблокирована и отображаться для вас в начале.",
"poll_wizardDescription": "Автоматическое создавайте несколько опций путем ввода произвольного количества дат и временных сегментов",
"poll_comment_disabled": "Опубликуйте этот опрос с помощью кнопки ✓ для включения комментариев",
"poll_wizardDescription": "Автоматически создавайте несколько опций путем ввода произвольного количества дат и временных сегментов",
"poll_comment_disabled": "Опубликуйте этот опрос с помощью кнопки ✓ для включения комментариев.",
"oo_cantUpload": "Загрузка запрещена, если присутствуют другие пользователи.",
"oo_uploaded": "Ваша загрузка завершена. Нажмите OK, чтобы перезагрузить страницу или отменить, чтобы остаться в режиме чтения.",
"canvas_imageEmbed": "Вставьте изображение с вашего компьютера",
@ -367,13 +367,13 @@
"fm_padIsOwnedOther": "Этот пэд принадлежит другому пользователю",
"fm_deletedPads": "Эти пэды больше не существуют на сервере, они были удалены с вашего CryptDrive: {0}",
"fm_tags_name": "Имя тэга",
"printCSS": "Пользовательские настройки вида (CSS)",
"viewEmbedTag": "Чтобы встроить данный документ вставьте iframe в нужную страницу. Вы можете настроить внешний вид используя CSS и HTML атрибуты. ",
"printCSS": "Пользовательские настройки вида (CSS):",
"viewEmbedTag": "Чтобы встроить данный документ, вставьте iframe в нужную страницу. Вы можете настроить внешний вид используя CSS и HTML атрибуты.",
"debug_getGraphText": "Это код DOT для генерации графика истории этого документа:",
"fm_ownedPadsName": "Собственный",
"fm_info_anonymous": "Вы не вошли в учетную запись, поэтому срок действия ваших пэдов истечет через 3 месяца (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">find out more</a>). Они хранятся в вашем браузере, поэтому очистка истории может привести к их исчезновению..<br><a href=\"/register/\">Sign up</a> or <a href=\"/login/\">Log in</a> to keep them alive.<br>",
"fm_backup_title": "Резервная ссылка",
"fm_burnThisDriveButton": "Удалить всю информацию, хранящуюся на CryptPad в браузере.",
"fm_burnThisDriveButton": "Удалить всю информацию, хранящуюся от CryptPad в браузере",
"fm_tags_used": "Количество использований",
"fm_restoreDrive": "Восстановление прежнего состояния диска. Для достижения наилучших результатов не вносите изменения в диск, пока этот процесс не будет завершен.",
"fm_passwordProtected": "Этот документ защищен паролем",
@ -415,7 +415,7 @@
"fm_info_sharedFolder": "Это общая папка. Вы не вошли в систему, поэтому можете получить к ней доступ только в режиме только для чтения.<br><a href=\"/register/\">Sign up</a> или <a href=\"/login/\">Log in</a> для импорта на CryptDrive и его изменения.",
"fo_moveFolderToChildError": "Вы не можете переместить папку в одну из нее следующую",
"fo_unavailableName": "Файл или папка с таким же именем уже существуют в новом месте. Переименуйте элемент и повторите попытку.",
"fs_migration": "Ваш CryptDrive обновляется до новой версии. В результате, текущая страница должна быть перезагружена.<br><strong> перезагрузите эту страницу, чтобы продолжить ей пользоваться.</strong>.",
"fs_migration": "Ваш CryptDrive обновляется до новой версии. В результате, текущая страница должна быть перезагружена.<br><strong> перезагрузите эту страницу, чтобы продолжить ей пользоваться.</strong>",
"login_accessDrive": "Доступ к хранилищу",
"login_orNoLogin": "или",
"login_noSuchUser": "Неверный логин или пароль. Попробуйте еще раз или зарегистрируйтесь",
@ -448,5 +448,59 @@
"settings_exportWarning": "Примечание: этот инструмент все еще находится в бета-версии и может иметь проблемы со масштабируемостью. Для повышения производительности рекомендуется оставить данную вкладку сфокусированной.",
"settings_exportCancel": "Вы уверены, что хотите отменить экспорт? В следующий раз вам придется начинать все сначала.",
"settings_export_reading": "Читаем ваше хранилище...",
"settings_export_download": "Скачиваем и расшифровываем ваши документы..."
"settings_export_download": "Скачиваем и расшифровываем ваши документы...",
"contacts_request": "<em>{0}</em> хотел бы добавить вас в список контактов. <b>Принять <b>?",
"contacts_confirmRemove": "Вы уверены, что хотите удалить <em>1{0}</em>2 из ваших контактов?",
"register_acceptTerms": "Я принимаю <a href='/terms.html' tabindex='-1'>1 условия пользования</a>",
"register_warning": "Мы не сможем восстановить ваши данные, если вы потеряете пароль, так как мы не имеем доступа к ним.",
"settings_backupCategory": "Резервное копирование",
"settings_backup": "Резервная копия",
"settings_backupHint2": "Загрузите текущее содержимое всех ваших пэдов. Пэды будут загружены в читаемом формате, если такой формат доступен.",
"settings_export_compressing": "Данные сжимаются..",
"settings_export_done": "Ваше скачивание завершено!",
"settings_exportError": "Посмотреть ошибки",
"settings_exportErrorDescription": "Мы не смогли добавить в экспорт следующие документы:",
"settings_exportErrorEmpty": "Этот документ не может быть экспортирован (пустое или недостоверное содержимое).",
"settings_exportErrorMissing": "Этот документ отсутствует на наших серверах (истек или удален владельцем)",
"settings_exportErrorOther": "При попытке экспорта данного документа возникла ошибка: {0}",
"settings_resetNewTitle": "Очистить хриналище",
"settings_resetButton": "Удалить",
"settings_reset": "Удалите все файлы и папки с CryptDrive",
"settings_resetDone": "Ваше хранилище теперь пустое!",
"settings_resetError": "Неправильный текст верификации. Ваш CryptDrive не был изменен.",
"settings_resetTipsAction": "Сброс",
"settings_resetTips": "Подсказки",
"settings_resetTipsButton": "Сброс доступных подсказок в CryptDrive",
"settings_resetTipsDone": "Теперь все подсказки снова видны.",
"settings_thumbnails": "Иконки",
"settings_disableThumbnailsAction": "Отключить создание иконок в вашем хранилище",
"settings_disableThumbnailsDescription": "Иконки автоматически создаются и сохраняются в браузере при посещении нового документа. Вы можете отключить эту функцию здесь.",
"settings_resetThumbnailsAction": "Очистить",
"settings_resetThumbnailsDescription": "Очистить все иконки документов, хранящиеся в вашем браузере.",
"settings_resetThumbnailsDone": "Все иконки были удалены.",
"settings_import": "Импортировать",
"settings_importDone": "Импортирование завершено",
"settings_autostoreTitle": "Хранилище документов в CryptDrive",
"settings_autostoreYes": "Автоматически",
"settings_autostoreNo": "Вручную (никогда не спрашивать)",
"settings_autostoreMaybe": "Вручную (всегда спрашивать)",
"settings_userFeedbackTitle": "Обратная связь",
"settings_userFeedbackHint2": "Содержимое вашего документа никогда не будет передаваться на сервер.",
"fm_alert_anonymous": "Здравствуйте, в настоящее время вы используете CryptPad анонимно, это нормально, но ваши пэды могут быть удалены после периода бездействия. Мы отключили расширенные возможности хранилища для анонимных пользователей, потому что хотим быть уверенными, что это небезопасное место для хранения вещей. Вы можете <a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">1читать далее</a>2 о том, почему мы это делаем и почему вам стоит <a href=\"/register/\">зарегистрироваться</a>4 and <a href=\"/login/\">5Log in</a>6.",
"settings_resetPrompt": "Это действие удалит все документы с диска.<br>Вы уверены, что хотите продолжить?<br>Напишите \"<em>Я люблю CryptPad</em>\" для подтверждения.",
"settings_importTitle": "Импортируйте последние документы данного браузера в ваше хранилище",
"settings_importConfirm": "Вы уверены, что хотите импортировать последние документы из этого браузера в хранилище вашего пользователя?",
"settings_userFeedbackHint1": "CryptPad держит очень простую обратную связь с сервером, чтобы мы знали, как улучшить ваше пользование.\n",
"settings_userFeedback": "Включить телеметрию",
"settings_deleteTitle": "Удаление аккаунта",
"settings_deleteHint": "Удаление аккаунта является постоянным. Ваш CryptDrive и список пэдов будут удалены с сервера. Остальные ваши пэды будут удалены через 90 дней, если никто другой не сохранил их в CryptDrive.",
"settings_deleteButton": "Удалить ваш аккаунт",
"settings_deleteModal": "Обменивайтесь следующей информацией с администратором CryptPad, чтобы удалить ваши данные с сервера.",
"settings_deleteConfirm": "Нажмите OK, чтобы удалить ваш аккаунт навсегда. Вы уверены?",
"settings_deleted": "Ваша учетная запись пользователя удалена. Нажмите OK, чтобы перейти на главную страницу.",
"settings_publicSigningKey": "Публичный ключ подписи",
"settings_usage": "Использование",
"settings_usageTitle": "Смотрите общий размер ваших прикрепленных документов в мегабайтах",
"settings_pinningNotAvailable": "Прикрепленные документы доступны только зарегистрированным пользователям.",
"settings_pinningError": "Что-то пошло не так"
}

@ -82,6 +82,7 @@ define([
var faCollapseAll = 'fa-minus-square-o';
var faShared = 'fa-shhare-alt';
var faReadOnly = 'fa-eye';
var faOpenInCode = 'cptools-code';
var faRename = 'fa-pencil';
var faColor = 'cptools-palette';
var faTrash = 'fa-trash';
@ -91,7 +92,7 @@ define([
var faEmpty = 'fa-trash-o';
var faRestore = 'fa-repeat';
var faShowParent = 'fa-location-arrow';
var faDownload = 'cptools-file';
var faDownload = 'fa-download';
var $folderIcon = $('<span>', {
"class": faFolder + " cptools cp-app-drive-icon-folder cp-app-drive-content-icon"
});
@ -343,6 +344,10 @@ define([
'tabindex': '-1',
'data-icon': faReadOnly,
}, Messages.fc_open_ro)),
h('li', h('a.cp-app-drive-context-openincode.dropdown-item', {
'tabindex': '-1',
'data-icon': faOpenInCode,
}, Messages.fc_openInCode)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-expandall.dropdown-item', {
'tabindex': '-1',
@ -353,22 +358,20 @@ define([
'data-icon': faCollapseAll,
}, Messages.fc_collapseAll)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faColor,
}, Messages.fc_color)),
h('li', h('a.cp-app-drive-context-download.dropdown-item', {
h('li', h('a.cp-app-drive-context-openparent.dropdown-item', {
'tabindex': '-1',
'data-icon': faDownload,
}, Messages.download_mt_button)),
'data-icon': faShowParent,
}, Messages.fm_openParent)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-share.dropdown-item', {
'tabindex': '-1',
'data-icon': 'fa-shhare-alt',
}, Messages.shareButton)),
h('li', h('a.cp-app-drive-context-openparent.dropdown-item', {
h('li', h('a.cp-app-drive-context-download.dropdown-item', {
'tabindex': '-1',
'data-icon': faShowParent,
}, Messages.fm_openParent)),
'data-icon': faDownload,
}, Messages.download_mt_button)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-newfolder.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faFolder,
@ -377,10 +380,6 @@ define([
'tabindex': '-1',
'data-icon': faSharedFolder,
}, Messages.fc_newsharedfolder)),
h('li', h('a.cp-app-drive-context-hashtag.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faTags,
}, Messages.fc_hashtag)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
@ -401,7 +400,7 @@ define([
h('a.cp-app-drive-context-newdocmenu.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-plus",
}, Messages.fm_morePads || "More pads"), //XXX
}, Messages.fm_morePads),
h("ul.dropdown-menu", [
h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
@ -439,6 +438,15 @@ define([
'tabindex': '-1',
'data-icon': faRename,
}, Messages.fc_rename)),
h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faColor,
}, Messages.fc_color)),
h('li', h('a.cp-app-drive-context-hashtag.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faTags,
}, Messages.fc_hashtag)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-delete.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faTrash,
@ -455,6 +463,7 @@ define([
'tabindex': '-1',
'data-icon': faDelete,
}, Messages.fc_remove_sharedfolder)),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
'tabindex': '-1',
'data-icon': faProperties,
@ -1092,7 +1101,8 @@ define([
hide.push('openparent');
}
if (!$element.is('.cp-border-color-file')) {
hide.push('download');
//hide.push('download');
hide.push('openincode');
}
if ($element.is('.cp-app-drive-element-file')) {
// No folder in files
@ -1103,6 +1113,11 @@ 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
var metadata = manager.getFileData(manager.find(path));
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
hide.push('openincode');
}
} else if ($element.is('.cp-app-drive-element-sharedf')) {
if (containsFolder) {
// More than 1 folder selected: cannot create a new subfolder
@ -1112,6 +1127,7 @@ define([
}
containsFolder = true;
hide.push('openro');
hide.push('openincode');
hide.push('hashtag');
hide.push('delete');
//hide.push('deleteowned');
@ -1124,6 +1140,7 @@ define([
}
containsFolder = true;
hide.push('openro');
hide.push('openincode');
hide.push('properties');
hide.push('share');
hide.push('hashtag');
@ -1157,6 +1174,8 @@ define([
hide.push('openparent');
hide.push('hashtag');
hide.push('download');
hide.push('share');
hide.push('openincode'); // can't because of race condition
}
if (containsFolder && paths.length > 1) {
// Cannot open multiple folders
@ -1173,7 +1192,7 @@ define([
show = ['newfolder', 'newsharedfolder', 'newdoc'];
break;
case 'tree':
show = ['open', 'openro', 'expandall', 'collapseall', 'color', 'download', 'share', 'rename', 'delete', 'deleteowned', 'removesf', 'properties', 'hashtag'];
show = ['open', 'openro', 'openincode', 'expandall', 'collapseall', 'color', 'download', 'share', 'rename', 'delete', 'deleteowned', 'removesf', 'properties', 'hashtag'];
break;
case 'default':
show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'hashtag'];
@ -3593,11 +3612,32 @@ define([
});
});
};
var downloadFolder = function (folderElement, folderName, sfId) {
var todo = function (data) {
data.folder = folderElement;
data.sharedFolderId = sfId;
data.folderName = Util.fixFileName(folderName) + '.zip';
APP.FM.downloadFolder(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
};
todo({
uo: proxy,
sf: folders,
});
};
$contextMenu.on("click", "a", function(e) {
e.stopPropagation();
var paths = $contextMenu.data('paths');
var pathsList = [];
var type = $contextMenu.attr('data-menu-type');
var $this = $(this);
var el, data;
if (paths.length === 0) {
@ -3606,11 +3646,11 @@ define([
return;
}
if ($(this).hasClass("cp-app-drive-context-rename")) {
if ($this.hasClass("cp-app-drive-context-rename")) {
if (paths.length !== 1) { return; }
displayRenameInput(paths[0].element, paths[0].path);
}
else if ($(this).hasClass("cp-app-drive-context-color")) {
else if ($this.hasClass("cp-app-drive-context-color")) {
var currentColor = getFolderColor(paths[0].path);
pickFolderColor(paths[0].element, currentColor, function (color) {
paths.forEach(function (p) {
@ -3619,24 +3659,24 @@ define([
refresh();
});
}
else if($(this).hasClass("cp-app-drive-context-delete")) {
else if($this.hasClass("cp-app-drive-context-delete")) {
if (!APP.loggedIn) {
return void deletePaths(paths);
}
paths.forEach(function (p) { pathsList.push(p.path); });
moveElements(pathsList, [TRASH], false, refresh);
}
else if ($(this).hasClass('cp-app-drive-context-deleteowned')) {
else if ($this.hasClass('cp-app-drive-context-deleteowned')) {
deleteOwnedPaths(paths);
}
else if ($(this).hasClass('cp-app-drive-context-open')) {
else if ($this.hasClass('cp-app-drive-context-open')) {
paths.forEach(function (p) {
var $element = p.element;
$element.click();
$element.dblclick();
});
}
else if ($(this).hasClass('cp-app-drive-context-openro')) {
else if ($this.hasClass('cp-app-drive-context-openro')) {
paths.forEach(function (p) {
var el = manager.find(p.path);
if (paths[0].path[0] === SHARED_FOLDER && APP.newSharedFolder) {
@ -3654,10 +3694,29 @@ define([
openFile(null, href);
});
}
else if ($(this).hasClass('cp-app-drive-context-expandall') ||
$(this).hasClass('cp-app-drive-context-collapseall')) {
else if ($this.hasClass('cp-app-drive-context-openincode')) {
if (paths.length !== 1) { return; }
var p = paths[0];
el = manager.find(p.path);
var metadata = manager.getFileData(el);
var simpleData = {
title: metadata.filename || metadata.title,
href: metadata.href,
password: metadata.password,
channel: metadata.channel,
};
nThen(function (waitFor) {
common.sessionStorage.put(Constants.newPadFileData, JSON.stringify(simpleData), waitFor());
common.sessionStorage.put(Constants.newPadPathKey, currentPath, waitFor());
}).nThen(function () {
common.openURL('/code/');
});
}
else if ($this.hasClass('cp-app-drive-context-expandall') ||
$this.hasClass('cp-app-drive-context-collapseall')) {
if (paths.length !== 1) { return; }
var opened = $(this).hasClass('cp-app-drive-context-expandall');
var opened = $this.hasClass('cp-app-drive-context-expandall');
var openRecursive = function (path) {
setFolderOpened(path, opened);
var folderContent = manager.find(path);
@ -3680,17 +3739,49 @@ define([
openRecursive(paths[0].path);
refresh();
}
else if ($(this).hasClass('cp-app-drive-context-download')) {
else if ($this.hasClass('cp-app-drive-context-download')) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
if (!manager.isFile(el)) { return; }
data = manager.getFileData(el);
APP.FM.downloadFile(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
var path = paths[0];
el = manager.find(path.path);
// folder
if (manager.isFolder(el)) {
// folder
var name, folderEl;
if (!manager.isSharedFolder(el)) {
name = path.path[path.path.length - 1];
folderEl = el;
downloadFolder(folderEl, name);
}
// shared folder
else {
data = manager.getSharedFolderData(el);
name = data.title;
folderEl = manager.find(path.path.concat("root"));
downloadFolder(folderEl, name, el);
}
}
// file
else if (manager.isFile(el)) {
// imported file
if (path.element.is(".cp-border-color-file")) {
data = manager.getFileData(el);
APP.FM.downloadFile(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
}
// pad
else {
data = manager.getFileData(el);
APP.FM.downloadPad(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
}
}
}
else if ($(this).hasClass('cp-app-drive-context-share')) {
else if ($this.hasClass('cp-app-drive-context-share')) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
var parsed, modal;
@ -3741,7 +3832,7 @@ define([
wide: Object.keys(friends).length !== 0
});
}
else if ($(this).hasClass('cp-app-drive-context-newfolder')) {
else if ($this.hasClass('cp-app-drive-context-newfolder')) {
if (paths.length !== 1) { return; }
var onFolderCreated = function (err, info) {
if (err) { return void logError(err); }
@ -3754,21 +3845,21 @@ define([
}
manager.addFolder(paths[0].path, null, onFolderCreated);
}
else if ($(this).hasClass('cp-app-drive-context-newsharedfolder')) {
else if ($this.hasClass('cp-app-drive-context-newsharedfolder')) {
if (paths.length !== 1) { return; }
addSharedFolderModal(function (obj) {
if (!obj) { return; }
manager.addSharedFolder(paths[0].path, obj, refresh);
});
}
else if ($(this).hasClass("cp-app-drive-context-newdoc")) {
var ntype = $(this).data('type') || 'pad';
else if ($this.hasClass("cp-app-drive-context-newdoc")) {
var ntype = $this.data('type') || 'pad';
var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
common.sessionStorage.put(Constants.newPadPathKey, path2, function () {
common.openURL('/' + ntype + '/');
});
}
else if ($(this).hasClass("cp-app-drive-context-properties")) {
else if ($this.hasClass("cp-app-drive-context-properties")) {
if (type === 'trash') {
var pPath = paths[0].path;
if (paths.length !== 1 || pPath.length !== 4) { return; }
@ -3788,7 +3879,7 @@ define([
UI.alert($prop[0], undefined, true);
});
}
else if ($(this).hasClass("cp-app-drive-context-hashtag")) {
else if ($this.hasClass("cp-app-drive-context-hashtag")) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
data = manager.getFileData(el);
@ -3796,7 +3887,7 @@ define([
var href = data.href || data.roHref;
common.updateTags(href);
}
else if ($(this).hasClass("cp-app-drive-context-empty")) {
else if ($this.hasClass("cp-app-drive-context-empty")) {
if (paths.length !== 1 || !paths[0].element
|| !manager.comparePath(paths[0].path, [TRASH])) {
log(Messages.fm_forbidden);
@ -3807,13 +3898,13 @@ define([
manager.emptyTrash(refresh);
});
}
else if ($(this).hasClass("cp-app-drive-context-remove")) {
else if ($this.hasClass("cp-app-drive-context-remove")) {
return void deletePaths(paths);
}
else if ($(this).hasClass("cp-app-drive-context-removesf")) {
else if ($this.hasClass("cp-app-drive-context-removesf")) {
return void deletePaths(paths);
}
else if ($(this).hasClass("cp-app-drive-context-restore")) {
else if ($this.hasClass("cp-app-drive-context-restore")) {
if (paths.length !== 1) { return; }
var restorePath = paths[0].path;
var restoreName = paths[0].path[paths[0].path.length - 1];
@ -3830,7 +3921,7 @@ define([
manager.restore(restorePath, refresh);
});
}
else if ($(this).hasClass("cp-app-drive-context-openparent")) {
else if ($this.hasClass("cp-app-drive-context-openparent")) {
if (paths.length !== 1) { return; }
var parentPath = paths[0].path.slice();
if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; }

@ -12,7 +12,7 @@ define([
'/customize/credential.js',
'/customize/application_config.js',
'/api/config',
'/settings/make-backup.js',
'/common/make-backup.js',
'/common/common-feedback.js',
'/common/jscolor.js',
@ -1083,18 +1083,9 @@ define([
var exportDrive = function () {
Feedback.send('FULL_DRIVE_EXPORT_START');
var todo = function (data, filename) {
var getPad = function (data, cb) {
sframeChan.query("Q_CRYPTGET", data, function (err, obj) {
if (err) { return void cb(err); }
if (obj.error) { return void cb(obj.error); }
cb(null, obj.data);
}, { timeout: 60000 });
};
var ui = createExportUI();
var bu = Backup.create(data, getPad, function (blob, errors) {
console.log(blob);
var bu = Backup.create(data, common.getPad, function (blob, errors) {
saveAs(blob, filename);
sframeChan.event('EV_CRYPTGET_DISCONNECT');
ui.complete(function () {

Loading…
Cancel
Save