From f55c8c6b0458b2eac483a832d23be2ba6a0e5ff2 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Tue, 13 Aug 2019 16:09:06 +0200 Subject: [PATCH] Open plain text files in code app && plain text files thumbnail --- www/common/common-constants.js | 1 + www/common/common-thumbnail.js | 30 ++++++++++++- www/common/common-ui-elements.js | 29 +++++++++++-- www/common/common-util.js | 29 +++++++------ www/common/cryptpad-common.js | 67 +++++++++++++++++++++++++++++- www/common/sframe-app-framework.js | 2 +- www/common/sframe-common-file.js | 2 +- www/common/sframe-common-outer.js | 31 +++++++++++++- www/drive/inner.js | 24 ++++++++--- 9 files changed, 190 insertions(+), 25 deletions(-) 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 34d44daf5..4b9d7e595 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,11 @@ define([ 'application/pdf' ]; - Thumb.isSupportedType = function (type) { + Thumb.isSupportedType = function (file) { + 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 +169,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 +196,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 bc465cdfb..831f1bb71 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2274,7 +2274,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); @@ -2286,7 +2289,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 @@ -2296,7 +2300,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, @@ -2453,7 +2457,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 124a72344..171d37624 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -319,21 +319,26 @@ define([], function () { return window.innerHeight < 800 || window.innerWidth < 800; }; - Util.isPlainTextFile = function (metadata) { - if (!metadata || !metadata.href) { return; } - console.log("%c" + metadata.title + " : " + metadata.fileType, "color: #3a7"); - console.log(metadata); - var href = metadata.roHref || metadata.href; - // is it a file ? - if (!href || href.indexOf("/file/") === -1) { return false; } + // return an + 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 (metadata.fileType.indexOf("text/") === 0) { return true; } + if (type && type.indexOf("text/") === 0) { return true; } // no type and no file extension -> let's guess it's plain text - var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(metadata.title) || []; - if (!metadata.fileType && !parsedName[2]) { return true; } + var parsedName = Util.parseFilename(name); + if (!type && name && !parsedName.ext) { return true; } // other exceptions - if (metadata.fileType === 'application/x-javascript') { return true; } - if (metadata.fileType === 'application/xml') { return true; } + if (type === 'application/x-javascript') { return true; } + if (type === 'application/xml') { return true; } return false; }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index fef46951c..f6527bf26 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -9,11 +9,12 @@ define([ '/common/outer/local-store.js', '/common/outer/worker-channel.js', '/common/outer/login-block.js', + '/file/file-crypto.js', '/customize/application_config.js', '/bower_components/nthen/index.js', ], function (Config, Messages, Util, Hash, - Messaging, Constants, Feedback, LocalStore, Channel, Block, + Messaging, Constants, Feedback, LocalStore, Channel, Block, FileCrypto, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to @@ -567,6 +568,64 @@ 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) { + 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) { + var fileType = 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; @@ -1263,6 +1322,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-app-framework.js b/www/common/sframe-app-framework.js index f1068a6de..92311b44b 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -454,7 +454,7 @@ define([ return; } if (!mediaTagEmbedder) { console.log('mediaTagEmbedder missing'); return; } - if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; } + if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; } var privateDat = cpNfInner.metadataMgr.getPrivateData(); var origin = privateDat.fileHost || privateDat.origin; var src = data.src = origin + data.src; diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index 099176afd..573f39357 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 fba08279a..588c2bfad 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -317,6 +317,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; @@ -357,6 +360,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) { @@ -808,6 +813,22 @@ define([ }); }); + sframeChan.on('Q_GET_FILE_THUMBNAIL', function (data, cb) { + if (!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; @@ -1080,11 +1101,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 () { @@ -1093,6 +1114,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 d3e06e39b..c6fdc6067 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1025,10 +1025,15 @@ define([ hide.push('openro'); // Remove open 'view' mode } // if it's not a plain text file + var isPlainTextFile = false; var metadata = manager.getFileData(manager.find(path)); - if (!metadata || !Util.isPlainTextFile(metadata)) { - hide.push('openincode'); + if (metadata) { + var href = metadata.roHref || metadata.href; + if (href && Hash.parsePadUrl(href).type === "file") { + isPlainTextFile = Util.isPlainTextFile(metadata.fileType, metadata.title); + } } + if (!isPlainTextFile) { hide.push('openincode'); } } else if ($element.is('.cp-app-drive-element-sharedf')) { if (containsFolder) { // More than 1 folder selected: cannot create a new subfolder @@ -3539,11 +3544,20 @@ define([ } else if ($(this).hasClass('cp-app-drive-context-openincode')) { paths.forEach(function (p) { - console.info("p", p); var el = manager.find(p.path); var metadata = manager.getFileData(el); - console.log(el); - // open code from template + 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') ||