Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File datatable editing #63

Merged
merged 8 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions corere/apps/file_datatable/static/file_datatable/file_datatable.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ function create_file_table_config(table_path, readonly, is_submission, file_url_
processing: true,
stateSave: true,
paging: true,
select: 'single',
autoWidth: false,
// dom: 'Bftlp',
dom: 'Bfrtpl',
Expand All @@ -60,13 +59,19 @@ function create_file_table_config(table_path, readonly, is_submission, file_url_
{
data: 'path',
render: function(data,type,row,meta){
return row[0];
if(readonly) { return row[0]; }

//TODO: Stale, doing name first
return row[0] + '<span style="float:right;"><button class="btn btn-secondary btn-sm" type="button" onclick="show_edit_path_modal(\''+file_url_base+'\', \''+encodeURIComponent(row[1])+'\', \''+encodeURIComponent(row[0])+'\')" title="Edit file path" aria-label="Edit file path"><span class="fas fa-pencil-alt"></span></button></span>';

}
},
{
data: 'name',
render: function(data,type,row,meta){
return row[1];
if(readonly) { return row[1]; }

return row[1] + '<span style="float:right;"><button class="btn btn-secondary btn-sm" type="button" onclick="show_edit_name_modal(\''+file_url_base+'\', \''+encodeURIComponent(row[0])+'\', \''+encodeURIComponent(row[1])+'\')" title="Edit file name" aria-label="Edit file name"><span class="fas fa-pencil-alt"></span></button></span>';
}
},
],
Expand All @@ -78,4 +83,90 @@ function create_file_table_config(table_path, readonly, is_submission, file_url_
buttons: top_buttons
}
return config
}

//TODO: When we show these modals, we need to look at the length of the path/name (that is hidden) to determine the length of the input field

function show_edit_name_modal(file_url_base, file_path, old_name){
name_length = 260 - decodeURIComponent(file_path).length;
$('#name_modal_file_name_new').attr('maxlength', name_length);
$('#name_modal_file_name_old').val(decodeURIComponent(old_name));
$('#name_modal_file_path').val(file_path);
$('#name_modal_file_url_base').val(file_url_base);
$('#name_modal').modal('show');
}

function show_edit_path_modal(file_url_base, file_name, old_path){
path_length = 260 - decodeURIComponent(file_name).length;
$('#path_modal_file_path_new').attr('maxlength', path_length);
$('#path_modal_file_path_old').val(decodeURIComponent(old_path));
$('#path_modal_file_name').val(file_name);
$('#path_modal_file_url_base').val(file_url_base);
$('#path_modal').modal('show');
}

//TODO: Why are we only encoding one of the two text fields in both?
function submit_edit_name_modal_and_reload(){
file_name_old = encodeURIComponent($('#name_modal_file_name_old').val())
file_name_new = $('#name_modal_file_name_new').val()
file_url_base = $('#name_modal_file_url_base').val()
file_path = $('#name_modal_file_path').val()

var regex1 = /[*?"<>|;#:\\\/]/;
var regex2 = /\.\./;
if($.trim(file_name_new).length === 0 || regex1.test(file_name_new) || regex2.test(file_name_new)) {
$('#name_modal_sanitize_error').removeAttr('hidden');
return;
}

old_full_path = file_path + file_name_old
new_full_path = file_path + file_name_new

rename_url = file_url_base+'renamefile/?old_path='+old_full_path+'&new_path='+new_full_path
rename_and_refresh(rename_url, clear_name_modal, show_name_modal_generic_error)
}

function show_name_modal_generic_error() {
$('#name_modal_unexpected_error').removeAttr('hidden')
}

function clear_name_modal() {
$('#name_modal_sanitize_error').attr("hidden",true);
$('#name_modal_unexpected_error').attr("hidden",true);
$('#name_modal_file_name_new').val('')
$('#name_modal').modal('hide');
}

function submit_edit_path_modal_and_reload(){
file_path_old = encodeURIComponent($('#path_modal_file_path_old').val())
file_path_new = $('#path_modal_file_path_new').val()
file_url_base = $('#path_modal_file_url_base').val()
file_name = $('#path_modal_file_name').val()

//TODO: We need to ensure path begins and ends with /
var regex1 = /[^a-zA-Z0-9 /_\-\.]/;
var regex2 = /\.\./;
var regex3 = /\/\s*\//
if($.trim(file_path_new).length === 0 || regex1.test(file_path_new) || regex2.test(file_path_new) || regex3.test(file_path_new) || !file_path_new.startsWith("/") || !file_path_new.endsWith("/")) {
$('#path_modal_sanitize_error').removeAttr('hidden');
return;
}

old_full_path = file_path_old + file_name
new_full_path = file_path_new + file_name

rename_url = file_url_base+'renamefile/?old_path='+old_full_path+'&new_path='+new_full_path
rename_and_refresh(rename_url, clear_path_modal, show_path_modal_generic_error)

}

function show_path_modal_generic_error() {
$('#path_modal_unexpected_error').removeAttr('hidden')
}

function clear_path_modal() {
$('#path_modal_sanitize_error').attr("hidden",true);
$('#path_modal_unexpected_error').attr("hidden",true);
$('#path_modal_file_path_new').val('')
$('#path_modal').modal('hide');
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,68 @@
</table>
</div>

<div class="modal fade" id="name_modal" tabindex="-1" role="dialog" aria-labelledby="name_modalTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="name_modalTitle">Rename File</h5>
</div>
<div class="modal-body">
<table>
<div id="name_modal_sanitize_error" class="alert alert-danger" role="alert" hidden>File name cannot contain '..', be emtpy, or any of the following characters: * ? " < > | ; # : \ /</div>
<div id="name_modal_unexpected_error" class="alert alert-danger" role="alert" hidden>An unexpected error occurred. This may be due to a naming/path collision.</div>
<tr>
<td>Old Name: </td>
<td>&nbsp<input type="text" id="name_modal_file_name_old" value="" style="width:370px" disabled></td>
</tr>
<tr>
<td>New Name:</td>
<td>&nbsp<input type="text" id="name_modal_file_name_new" style="width:370px"></td>
</tr>
</table>
</div>
<div class="modal-footer">
<input type="hidden" id="name_modal_file_url_base" value="">
<input type="hidden" id="name_modal_file_path" value="">
<button type="button" class="btn btn-secondary" onclick="clear_name_modal()">Close</button>
<button type="button" id="name_modal_save_changes" class="btn btn-primary" onclick="submit_edit_name_modal_and_reload()">Save changes</button>
</div>
</div>
</div>
</div>

<div class="modal fade" id="path_modal" tabindex="-1" role="dialog" aria-labelledby="path_modalTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="path_modalTitle">Change Path</h5>
</div>
<div class="modal-body">
<table>
<div id="path_modal_sanitize_error" class="alert alert-danger" role="alert" hidden>File path cannot contain '..' or be emtpy. It must begin and end with '/'. Also, it must only contain the following characters: alphanumericals _ - . / and whitespace</div>
<div id="path_modal_unexpected_error" class="alert alert-danger" role="alert" hidden>An unexpected error occurred. This may be due to a naming/path collision.</div>
<tr>
<td>Old Path: </td>
<td>&nbsp<input type="text" id="path_modal_file_path_old" value="" style="width:370px" disabled></td>
</tr>
<tr>
<td>New Path:</td>
<td>&nbsp<input type="text" id="path_modal_file_path_new" style="width:370px"></td>
</tr>
</table>
</div>
<div class="modal-footer">
<input type="hidden" id="path_modal_file_url_base" value="">
<input type="hidden" id="path_modal_file_name" value="">
<button type="button" class="btn btn-secondary" onclick="clear_path_modal()">Close</button>
<button type="button" id="path_modal_save_changes" class="btn btn-primary" onclick="submit_edit_path_modal_and_reload()">Save changes</button>
</div>
</div>
</div>
</div>

{% comment %}If we want this library to truly be portable, all this CORE2 specific stuff should not be here? {% endcomment %}
{% comment %}Also file_datatable.js references delete_and_refresh which exists in the main app {% endcomment %}
{% load static %}
<script type="text/javascript" src="{% static 'file_datatable/file_datatable.js' %}?v=2"></script>
<script type="text/javascript">
Expand Down Expand Up @@ -41,6 +103,12 @@
document.getElementById('files_count_text').textContent = '('+$('#file_table_{{obj_type}}').DataTable().page.info().recordsTotal+')';
}
});
{% comment %}Also add a click listener for our modal{% endcomment%}
$("#name_modal_file_name_new").keyup(function(event) {
if (event.keyCode === 13) {
$("#name_modal_save_changes").click();
}
});
{% endif %}
$('#file_table_{{obj_type}}').DataTable(config)
} );
Expand Down
26 changes: 19 additions & 7 deletions corere/main/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,32 @@ def rename_submission_files(manuscript, files_dict_list):
files_folder = get_submission_files_path(manuscript, relative=True)
return _rename_files(repo_path, files_folder, files_dict_list)


def _rename_files(repo_path, files_folder, files_dict_list):
repo = git.Repo(repo_path)

# TODO: This doesn't handle name collisions that may happen during rename
for d in files_dict_list:
old_path = files_folder + d.get("old")
new_path = files_folder + d.get("new")

if os.path.exists(repo_path + new_path):
logger.warning(
"Error renaming files, new path already exists. Repo path: "
+ repo_path
+ " . Current file old path: "
+ old_path
+ " . New path: "
+ new_path
+ " ."
)
return False
try:
os.rename(repo_path + old_path, repo_path + new_path)
os.renames(repo_path + old_path, repo_path + new_path)
repo.index.add(repo_path + new_path)
repo.index.remove(repo_path + old_path)
repo.index.commit("File " + repo_path + old_path + " renamed to " + repo_path + new_path)
except OSError as e:
except Exception as e:
logger.error(
"Error renaming files. Likely due to name collision. Repo path: "
"Error renaming files. Cause uncertain. Repo path: "
+ repo_path
+ " . Current file old path: "
+ old_path
Expand All @@ -118,7 +128,9 @@ def _rename_files(repo_path, files_folder, files_dict_list):
+ " . Error "
+ str(e)
)
raise
return False

return True


def delete_manuscript_file(manuscript, file_path):
Expand Down Expand Up @@ -259,7 +271,7 @@ def download_all_manuscript_files(manuscript):

### The repo contains a sub-folder containing all the files. This is half to support downloading zips with a root folder


#TODO: Maybe rename these path endpoints to communicate they are getting the system path not the relative path in the code folder
def get_manuscript_repo_path(manuscript):
return settings.GIT_ROOT + "/" + str(manuscript.id) + "_-_manuscript_-_" + manuscript.slug + "/"

Expand Down
7 changes: 7 additions & 0 deletions corere/main/static/main/output.scss
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,13 @@ select.custom-select {
margin-right:0px !important;
}

//Dropzone error svg x
.dz-error-mark svg g g{
fill: #be2626;
fill-opacity: 1;
stroke-opacity: 0;
}

//Add back bootstrap 3 tiny buttons, for our grid. We aren't targeting mobile anyways
// .btn-group-xs > .btn, .btn-xs {
// padding: .25rem .4rem;
Expand Down
100 changes: 42 additions & 58 deletions corere/main/templates/main/file_upload.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,73 +55,57 @@
table.ajax.reload( function(){
document.getElementById('files_count_text').textContent = '('+table.page.info().recordsTotal+')'
});

{% comment %} // if(file.fullPath){
// full_name = "/" + file.fullPath
// } else {
// full_name = "/" + file.name
// }
// encode_full_name = encodeURIComponent(full_name)

// console.log(encode_full_name)

// document.getElementById("filesHeaderDiv").style.visibility = 'visible';

// $("#filestable").append(`<tr>
// <td> <button class="btn btn-secondary btn-sm" type="button" onclick="window.open('../downloadfile/?file_path=`+encode_full_name+`')"><span class="fas fa-file-download"></span></button></td>
// <td> <button class="btn btn-secondary btn-sm" type="button" onclick="delete_and_remove(this, '`+encode_full_name+`')"><span class="far fa-trash-alt"></span></button></td>
// <td class="filespath"><input name="file:`+full_name+`" type="text" style="width:500px" value='`+full_name+`'> </td>
// {% if obj_type == "submission" %}
// <td><img style="height:30px; width:30px" src="../newfilecheck/?file_path=`+encode_full_name+`"/></td>
// {% endif %}
// </tr>`);
{% endcomment %}

this.removeFile(file);
});
this.on("processing", function(file, xhr, data) {
{% comment %} // var deleteallbutton = document.getElementById("deleteallbutton");
// if(deleteallbutton) {
// deleteallbutton.disabled = true;
// }
// var downloadallbutton = document.getElementById("downloadallbutton");
// if(downloadallbutton) {
// downloadallbutton.disabled = true;
// }
// var submitbutton = document.getElementById("submit_fileslist");
// if(submitbutton) {
// submitbutton.disabled = true;
// }

//deleteallbutton.style.display = "none";
{% endcomment %}
});
this.on("queuecomplete", function(file, xhr, data) {
{% comment %} // var deleteallbutton = document.getElementById("deleteallbutton");
// if(deleteallbutton) {
// deleteallbutton.disabled = false;
// deleteallbutton.style.visibility = 'visible';
// }
// var downloadallbutton = document.getElementById("downloadallbutton");
// if(downloadallbutton) {
// downloadallbutton.disabled = false;
// downloadallbutton.style.visibility = 'visible';
// }
// var submitbutton = document.getElementById("submit_fileslist");
// if(submitbutton) {
// submitbutton.disabled = false;
// submitbutton.style.visibility = 'visible';
// }
//deleteallbutton.style.display = "block";
{% endcomment %}
});
this.on("sending", function(file, xhr, data) {
// if file is actually a folder
if(file.fullPath){
data.append("fullPath", file.fullPath);
}
});
}
},

accept: function(file, done) {
{% comment %} // similar regex in submit_edit_name_modal_and_reload {% endcomment %}

var regex1 = /[*?"<>|;#:\\\/]/;
var regex2 = /\.\./;

if(file.name.length === 0 || regex1.test(file.name) || regex2.test(file.name)) {
done('File name cannot be empty, contain .. or any of the following characters: * ? " < > | ; # : \ /');
}

//TODO: Set the correct length we need here
else if (file.name.length > 259) {
done("Filename + path exceeds 260 characters!");
}

{% comment %} console.log(file.name) {% endcomment %}
else if (file.fullPath) {
last_slash = file.fullPath.lastIndexOf('/');
folder_path = file.fullPath.slice(0, last_slash);
console.log(folder_path)
regex_path = /[^a-zA-Z0-9 /_\-\.]/;
//console.log(regex_path.test(folder_path));
if (file.fullPath > 260) {
done("Filename + path exceeds 260 characters!");
}
else if(regex2.test(folder_path)) {
done("File folder cannot contain ..");
}
else if(regex_path.test(folder_path)){
done("File folder can only contain the alphanumerics, _ - . and whitespace");
}
else {
done();
}
}

else {
done();
}
},
};
</script>
{% endif %}
Loading