Added file edit feature to My Files tab (#4243)

Ylian Saint-Hilaire committed Jul 13, 2022 at 20:23 UTC 636f801bd7667d4389813eef2b81c6efbe54132d
2 files changed +62 -21
meshuser.js
+31 -8
@@ -785,15 +785,15 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
785 // Check permissions
786 if ((user.siteadmin & 8) != 0) {
787 // Perform a file operation (Create Folder, Delete Folder, Delete File...)
788 - if (common.validateString(command.fileop, 4, 16) == false) return;
788 + if (common.validateString(command.fileop, 3, 16) == false) return;
789 var sendUpdate = true, path = meshPathToRealPath(command.path, user); // This will also check access rights
790 if (path == null) break;
791
792 if ((command.fileop == 'createfolder') && (common.IsFilenameValid(command.newfolder) == true)) {
793 // Create a new folder
794 - try { fs.mkdirSync(path + '/' + command.newfolder); } catch (ex) {
794 + try { fs.mkdirSync(parent.path.join(path, command.newfolder)); } catch (ex) {
795 try { fs.mkdirSync(path); } catch (ex) { }
796 - try { fs.mkdirSync(path + '/' + command.newfolder); } catch (ex) { }
796 + try { fs.mkdirSync(parent.path.join(path, command.newfolder)); } catch (ex) { }
797 }
798 }
799 else if (command.fileop == 'delete') {
@@ -822,14 +822,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
822 }
823 else if ((command.fileop == 'rename') && (common.IsFilenameValid(command.oldname) === true) && (common.IsFilenameValid(command.newname) === true)) {
824 // Rename
825 - try { fs.renameSync(path + '/' + command.oldname, path + '/' + command.newname); } catch (e) { }
825 + try { fs.renameSync(parent.path.join(path, command.oldname), parent.path.join(path, command.newname)); } catch (e) { }
826 }
827 else if ((command.fileop == 'copy') || (command.fileop == 'move')) {
828 // Copy or move of one or many files
829 - if (common.validateArray(command.names, 1) == false) return;
830 - var scpath = meshPathToRealPath(command.scpath, user); // This will also check access rights
829 + if (common.validateArray(command.name, 1) == false) return;
830 + var scpath = meshPathToRealPath(command.path, user); // This will also check access rights
831 if (scpath == null) break;
832 - // TODO: Check quota if this is a copy!!!!!!!!!!!!!!!!
832 + // TODO: Check quota if this is a copy
833 for (i in command.names) {
834 if (common.IsFilenameValid(command.names[i]) === true) {
835 var s = parent.path.join(scpath, command.names[i]), d = parent.path.join(path, command.names[i]);
@@ -837,8 +837,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
837 copyFile(s, d, function (op) { if (op != null) { fs.unlink(op, function (err) { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); }); } else { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } }, ((command.fileop == 'move') ? s : null));
838 }
839 }
840 + } else if (command.fileop == 'get') {
841 + // Get a short file and send it back on the web socket
842 + if (common.validateString(command.file, 1, 4096) == false) return;
843 + const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
844 + if (scpath == null) break;
845 + const filePath = parent.path.join(scpath, command.file);
846 + fs.stat(filePath, function (err, stat) {
847 + if ((err != null) || (stat == null) || (stat.size >= 204800)) return;
848 + fs.readFile(filePath, function (err, data) {
849 + if ((err != null) || (data == null)) return;
850 + command.data = data.toString('base64');
851 + ws.send(JSON.stringify(command)); // Send the file data back, base64 encoded.
852 + });
853 + });
854 + } else if (command.fileop == 'set') {
855 + // Set a short file transfered on the web socket
856 + if (common.validateString(command.file, 1, 4096) == false) return;
857 + if (typeof command.data != 'string') return;
858 + const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
859 + if (scpath == null) break;
860 + const filePath = parent.path.join(scpath, command.file);
861 + var data = null;
862 + try { data = Buffer.from(command.data, 'base64'); } catch (ex) { return; }
863 + fs.writeFile(filePath, data, function (err) { if (err == null) { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } });
864 }
841 -
865 if (sendUpdate == true) { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } // Fire an event causing this user to update this files
866 }
867 break;
views/default.handlebars
+31 -13
@@ -506,7 +506,7 @@
506 <input type=button id=p5SelectAllButton disabled="disabled" onclick="p5selectallfile();" value="Select All" />&nbsp;
507 <input type=button id=p5RenameFileButton disabled="disabled" value="Rename" onclick="p5renamefile();" />&nbsp;
508 <input type=button id=p5DeleteFileButton disabled="disabled" value="Delete" onclick="p5deletefile();" />&nbsp;
509 - <!--<input type=button id=p5ViewFileButton disabled="disabled" value="View" onclick="p5viewfile()" />&nbsp;-->
509 + <input type=button id=p5ViewFileButton disabled="disabled" value="Edit" onclick="p5viewfile()" />&nbsp;
510 <input type=button id=p5NewFolderButton disabled="disabled" value="New Folder" onclick="p5createfolder();" />&nbsp;
511 <input type=button id=p5UploadButton disabled="disabled" value="Upload" onclick="p5uploadFile()" />&nbsp;
512 <input type=button id=p5CutButton disabled="disabled" value="Cut" onclick="p5copyFile(1)" />&nbsp;
@@ -2915,6 +2915,30 @@
2915 account_managePhoneCodeValidate();
2916 break;
2917 }
2918 + case 'fileoperation': {
2919 + // View the file in the dialog box
2920 + var p5editSaveBack = function(b, tag) {
2921 + var data;
2922 + if (d4EditEncodingVal == 1) {
2923 + data = encode_utf8(Q('d4editorarea').value); // UTF8 encoding
2924 + } else {
2925 + data = Q('d4editorarea').value; // RAW encoding
2926 + }
2927 + meshserver.send({ action: 'fileoperation', fileop: 'set', path: tag.path, file: tag.file, data: btoa(data) });
2928 + }
2929 + setDialogMode(4, EscapeHtml(message.file), 3, p5editSaveBack, null, message);
2930 + QS('dialog').width = 'auto';
2931 + QS('dialog').bottom = '80px';
2932 + QS('dialog').top = QS('dialog').left = QS('dialog').right = '100px';
2933 + if (d4EditEncodingVal == 1) {
2934 + // UTF8 Encoding
2935 + Q('d4editorarea').value = decode_utf8(atob(message.data));
2936 + } else {
2937 + // RAW Encoding
2938 + Q('d4editorarea').value = atob(message.data);
2939 + }
2940 + break;
2941 + }
2942 case 'event': {
2943 if (!message.event.nolog) {
2944 if (currentNode && (message.event.nodeid == currentNode._id) && (currentDeviceEvents != null)) {
@@ -13656,10 +13680,10 @@
13680 function p5setActions() {
13681 var cc = getFileSelCount(), tc = getFileCount(), sfc = getFileSelCount(false); // In order: number of entires selected, number of total entries, number of selected entires that are files (not folders)
13682 QE('p5DeleteFileButton', (cc > 0) && (filetreelocation.length > 0));
13683 + QE('p5ViewFileButton', (cc == 1) && (sfc == 1) && (filetreelocation.length > 0));
13684 QE('p5NewFolderButton', filetreelocation.length > 0);
13685 QE('p5UploadButton', filetreelocation.length > 0);
13686 QE('p5RenameFileButton', (cc == 1) && (filetreelocation.length > 0));
13662 - //QE('p5ViewFileButton', (cc == 1) && (sfc == 1) && (filetreelocation.length > 0));
13687 QE('p5SelectAllButton', tc > 0);
13688 Q('p5SelectAllButton').value = (cc > 0 ? "Select None" : "Select All");
13689 QE('p5CutButton', (sfc > 0) && (cc == sfc));
@@ -13685,6 +13709,7 @@
13709 var isFilenameValid = (function(){ var x1=/^[^\\/:\*\?"<>\|]+$/, x2=/^\./, x3=/^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname){ return x1.test(fname)&&!x2.test(fname)&&!x3.test(fname)&&(fname[0] != '.'); } })();
13710 function p5uploadFile() { setDialogMode(2, "Upload File", 3, p5uploadFileEx, '<form method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame><input type=text name=link style=display:none id=p5uploadpath value="' + encodeURIComponentEx(filetreelinkpath) + '" /><input type=file name=files id=p5uploadinput style=width:100% multiple=multiple onchange="p5updateUploadDialogOk(\'p5uploadinput\')" /><input type=hidden name=authCookie value=' + authCookie + ' /><input type=submit id=p5loginSubmit style=display:none /><span id=p5confirmOverwriteSpan style=display:none><br /><label><input type=checkbox id=p5confirmOverwrite onchange="p5updateUploadDialogOk(\'p5uploadinput\')" />' + "Confirm overwrite?" + '</label></span></form>'); p5updateUploadDialogOk('p5uploadinput'); }
13711 function p5uploadFileEx() { Q('p5loginSubmit').click(); }
13712 + function p5GetFileInfo(name) { var filetreex = filetree; for (var i in filetreelocation) { if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) { filetreex = filetreex.f[filetreelocation[i]]; } } return filetreex.f[name]; }
13713 function p5updateUploadDialogOk() {
13714 // Check if these are files we can upload, remove all folders.
13715 var xallfiles = Q('p5uploadinput').files, files = [];
@@ -13712,27 +13737,20 @@
13737 }
13738 }
13739 }
13715 - /*
13740 function p5viewfile() {
13741 var checkboxes = document.getElementsByName('fc');
13742 for (var i = 0; i < checkboxes.length; i++) {
13743 if (checkboxes[i].checked) {
13720 - console.log(filetreelocation.join('/') + '/' + checkboxes[i].value); // TODO: Download and show this file
13721 - break;
13744 + var f = p5GetFileInfo(checkboxes[i].value); if (f == null) return;
13745 + if (f.s <= 204800) { meshserver.send({ action: 'fileoperation', fileop: 'get', path: filetreelocation, file: checkboxes[i].value, tag: 'edit' }); } else { messagebox("File Editor", "Only files less than 200k can be edited."); }
13746 + return;
13747 }
13748 }
13749 }
13725 - */
13726 -
13750 var p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0;
13751 function p5copyFile(cut) {
13752 var checkboxes = document.getElementsByName('fc'); p5clipboard = []; p5clipboardCut = cut, p5clipboardFolder = Clone(filetreelocation);
13730 - for (var i = 0; i < checkboxes.length; i++) {
13731 - if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) {
13732 - console.log('yy', checkboxes[i].value);
13733 - p5clipboard.push(checkboxes[i].value);
13734 - }
13735 - }
13753 + for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p5clipboard.push(checkboxes[i].value); } }
13754 p5updateClipview();
13755 }
13756 function p5pasteFile() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Confirm {0} of {1} entrie{2} to this location?", (p5clipboardCut == 0?'copy':'move'), p5clipboard.length, ((p5clipboard.length > 1)?'s':'')) } setDialogMode(2, "Paste", 3, p5pasteFileEx, x); }