diff --git a/www/common/common-constants.js b/www/common/common-constants.js
index 986e115e2..ac51dfbca 100644
--- a/www/common/common-constants.js
+++ b/www/common/common-constants.js
@@ -7,6 +7,7 @@ define(function () {
fileHashKey: 'FS_hash',
// sessionStorage
newPadPathKey: "newPadPath",
+ newPadFileData: "newPadFileData",
// Store
displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS',
diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js
index a6e5d5128..9d52f130b 100644
--- a/www/common/common-thumbnail.js
+++ b/www/common/common-thumbnail.js
@@ -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);
};
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 9aa313779..f3f2f5e6c 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -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 = $('
', { 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) {
diff --git a/www/common/common-util.js b/www/common/common-util.js
index 03c9e321b..262de76e9 100644
--- a/www/common/common-util.js
+++ b/www/common/common-util.js
@@ -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));
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index a22f4d57e..a2a2fe7a9 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -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];
diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js
index 32f031010..e2e2cd46c 100644
--- a/www/common/sframe-common-file.js
+++ b/www/common/sframe-common-file.js
@@ -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); }
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 104bdaa12..ba778a331 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -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;
@@ -1097,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 () {
@@ -1110,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();
diff --git a/www/drive/inner.js b/www/drive/inner.js
index 4292e5a7f..bdf74112a 100644
--- a/www/drive/inner.js
+++ b/www/drive/inner.js
@@ -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';
@@ -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',
@@ -1095,6 +1100,10 @@ define([
// We can only open parent in virtual categories
hide.push('openparent');
}
+ if (!$element.is('.cp-border-color-file')) {
+ //hide.push('download');
+ hide.push('openincode');
+ }
if ($element.is('.cp-app-drive-element-file')) {
// No folder in files
hide.push('color');
@@ -1104,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
@@ -1113,6 +1127,7 @@ define([
}
containsFolder = true;
hide.push('openro');
+ hide.push('openincode');
hide.push('hashtag');
hide.push('delete');
//hide.push('deleteowned');
@@ -1125,6 +1140,7 @@ define([
}
containsFolder = true;
hide.push('openro');
+ hide.push('openincode');
hide.push('properties');
hide.push('share');
hide.push('hashtag');
@@ -1159,6 +1175,7 @@ define([
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
@@ -1175,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'];
@@ -3677,6 +3694,25 @@ define([
openFile(null, href);
});
}
+ 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; }