mirror of
https://github.com/neocities/neocities.git
synced 2026-05-26 05:34:53 +00:00
api/delete strenghening, dashboard multi-select delete, obsolete site_files/delete (use api instead)
This commit is contained in:
+18
-5
@@ -203,6 +203,7 @@ post '/api/delete' do
|
||||
require_api_credentials
|
||||
|
||||
api_error 400, 'missing_filenames', 'you must provide files to delete' if params[:filenames].nil? || params[:filenames].empty?
|
||||
api_error 400, 'bad_filename', 'filenames must be an array, canceled deleting' unless params[:filenames].is_a?(Array)
|
||||
|
||||
paths = []
|
||||
params[:filenames].each do |path|
|
||||
@@ -210,18 +211,30 @@ post '/api/delete' do
|
||||
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
||||
end
|
||||
|
||||
if current_site.files_path(path) == current_site.files_path
|
||||
submitted_path = path
|
||||
|
||||
begin
|
||||
path = current_site.scrubbed_path submitted_path
|
||||
rescue ArgumentError
|
||||
api_error 400, 'bad_filename', "#{submitted_path} is not a valid filename, canceled deleting"
|
||||
end
|
||||
|
||||
if path.empty?
|
||||
api_error 400, 'cannot_delete_site_directory', 'cannot delete the root directory of the site'
|
||||
end
|
||||
|
||||
if current_site.invalid_path?(submitted_path) || current_site.invalid_path?(path)
|
||||
api_error 400, 'bad_filename', "#{submitted_path} is not a valid filename, canceled deleting"
|
||||
end
|
||||
|
||||
if path == 'index.html'
|
||||
api_error 400, 'cannot_delete_index', 'you cannot delete your index.html file, canceled deleting'
|
||||
end
|
||||
|
||||
if !current_site.file_exists?(path)
|
||||
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
||||
end
|
||||
|
||||
if path == 'index.html' || path == '/index.html'
|
||||
api_error 400, 'cannot_delete_index', 'you cannot delete your index.html file, canceled deleting'
|
||||
end
|
||||
|
||||
paths << path
|
||||
end
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
post '/site_files/delete' do
|
||||
require_login
|
||||
path = HTMLEntities.new.decode params[:filename]
|
||||
begin
|
||||
current_site.delete_file path
|
||||
rescue Sequel::NoExistingObject
|
||||
# the deed was presumably already done
|
||||
end
|
||||
flash[:success] = "Deleted #{Rack::Utils.escape_html params[:filename]}."
|
||||
|
||||
dirname = Pathname(path).dirname
|
||||
dir_query = dirname.nil? || dirname.to_s == '.' ? '' : "?dir=#{Rack::Utils.escape dirname}"
|
||||
|
||||
redirect "/dashboard#{dir_query}"
|
||||
end
|
||||
|
||||
post '/site_files/rename' do
|
||||
require_login
|
||||
path = HTMLEntities.new.decode params[:path]
|
||||
|
||||
+150
-2
@@ -8,13 +8,157 @@ function confirmFileRename(path) {
|
||||
}
|
||||
|
||||
function confirmFileDelete(name) {
|
||||
$('#deleteConfirmModal').data('delete-paths', [name]);
|
||||
$('#deleteFileName').text(name);
|
||||
$('#deleteFileCount').text('1');
|
||||
$('#deleteFileList').empty();
|
||||
$('#deleteSingleMessage').show();
|
||||
$('#deleteMultipleMessage').hide();
|
||||
$('#deleteConfirmModal').modal();
|
||||
}
|
||||
|
||||
function fileDelete() {
|
||||
$('#deleteFilenameInput').val($('#deleteFileName').html());
|
||||
$('#deleteFilenameForm').submit();
|
||||
var paths = $('#deleteConfirmModal').data('delete-paths') || [$('#deleteFileName').text()];
|
||||
var deleteButton = $('#deleteConfirmModal .btn-danger');
|
||||
|
||||
deleteButton.prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/api/delete',
|
||||
type: 'POST',
|
||||
data: {
|
||||
csrf_token: $('#deleteCSRFToken').val(),
|
||||
filenames: paths
|
||||
},
|
||||
success: function() {
|
||||
$('#deleteConfirmModal').modal('hide');
|
||||
setBulkSelectMode(false);
|
||||
alertClear();
|
||||
alertType('success');
|
||||
|
||||
if (paths.length === 1) {
|
||||
alertAdd($('<div>').text(paths[0]).html() + ' has been deleted.');
|
||||
} else {
|
||||
alertAdd(paths.length + ' items have been deleted.');
|
||||
}
|
||||
|
||||
reloadDashboardFiles();
|
||||
},
|
||||
error: function(xhr) {
|
||||
var message = 'Failed to delete file(s).';
|
||||
|
||||
try {
|
||||
message = JSON.parse(xhr.responseText).message || message;
|
||||
} catch(e) {
|
||||
}
|
||||
|
||||
alertClear();
|
||||
alertType('error');
|
||||
alertAdd($('<div>').text(message).html());
|
||||
$('#deleteConfirmModal').modal('hide');
|
||||
},
|
||||
complete: function() {
|
||||
deleteButton.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectedFilePaths() {
|
||||
return $('.bulk-select-checkbox:checked').map(function() {
|
||||
return $(this).val();
|
||||
}).get();
|
||||
}
|
||||
|
||||
function updateBulkActions() {
|
||||
var checkboxes = $('.bulk-select-checkbox');
|
||||
var checked = $('.bulk-select-checkbox:checked');
|
||||
var count = checked.length;
|
||||
var selectAll = $('#bulkSelectAll').get(0);
|
||||
|
||||
$('#selectedFileCount').text(count);
|
||||
$('#bulkDeleteButton').prop('disabled', count === 0);
|
||||
|
||||
checkboxes.each(function() {
|
||||
$(this).closest('.file').toggleClass('bulk-selected', $(this).prop('checked'));
|
||||
});
|
||||
|
||||
if (selectAll) {
|
||||
selectAll.checked = count > 0 && count === checkboxes.length;
|
||||
selectAll.indeterminate = count > 0 && count < checkboxes.length;
|
||||
}
|
||||
}
|
||||
|
||||
function setBulkSelectMode(enabled) {
|
||||
$('#filesDisplay').toggleClass('bulk-selecting', enabled);
|
||||
|
||||
if (!enabled) {
|
||||
$('.bulk-select-checkbox').prop('checked', false);
|
||||
}
|
||||
|
||||
updateBulkActions();
|
||||
}
|
||||
|
||||
function toggleBulkSelect(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
setBulkSelectMode(!$('#filesDisplay').hasClass('bulk-selecting'));
|
||||
}
|
||||
|
||||
function clearFileSelection() {
|
||||
$('.bulk-select-checkbox').prop('checked', false);
|
||||
updateBulkActions();
|
||||
}
|
||||
|
||||
function toggleSelectAllFiles(checked) {
|
||||
$('.bulk-select-checkbox').prop('checked', checked);
|
||||
updateBulkActions();
|
||||
}
|
||||
|
||||
function confirmBulkDelete() {
|
||||
var paths = selectedFilePaths();
|
||||
|
||||
if (paths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#deleteConfirmModal').data('delete-paths', paths);
|
||||
$('#deleteFileName').text(paths[0]);
|
||||
$('#deleteFileCount').text(paths.length);
|
||||
$('#deleteFileList').empty();
|
||||
|
||||
paths.forEach(function(path) {
|
||||
$('<li>').text(path).appendTo('#deleteFileList');
|
||||
});
|
||||
|
||||
$('#deleteSingleMessage').toggle(paths.length === 1);
|
||||
$('#deleteMultipleMessage').toggle(paths.length > 1);
|
||||
$('#deleteConfirmModal').modal();
|
||||
}
|
||||
|
||||
function initBulkFileSelection() {
|
||||
$('.bulk-select-checkbox').off('change.bulk').on('change.bulk', updateBulkActions);
|
||||
|
||||
$('.file').off('click.bulk').on('click.bulk', function(event) {
|
||||
if (!$('#filesDisplay').hasClass('bulk-selecting')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(event.target).closest('a, button, label').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var checkbox = $(this).find('.bulk-select-checkbox');
|
||||
if (checkbox.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkbox.prop('checked', !checkbox.prop('checked')).trigger('change');
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
updateBulkActions();
|
||||
}
|
||||
|
||||
function clickUploadFiles() {
|
||||
@@ -189,12 +333,16 @@ function reInitDashboardFiles() {
|
||||
document.getElementById('uploadButton').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
initBulkFileSelection();
|
||||
}
|
||||
|
||||
function reloadDashboardFiles() {
|
||||
var dir = $('#uploads input[name="dir"]').val();
|
||||
var bulkSelecting = $('#filesDisplay').hasClass('bulk-selecting');
|
||||
$.get('/dashboard/files?dir='+encodeURIComponent(dir), function(data) {
|
||||
$('#filesDisplay').html(data);
|
||||
$('#filesDisplay').toggleClass('bulk-selecting', bulkSelecting);
|
||||
reInitDashboardFiles();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -328,6 +328,58 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.files.bulk-selecting .select-files-button {
|
||||
background: #4F727B;
|
||||
}
|
||||
.files .bulk-actions {
|
||||
clear: both;
|
||||
display: none;
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(255, 255, 255, .11);
|
||||
border: 1px solid rgba(255, 255, 255, .18);
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: .9em;
|
||||
line-height: 1;
|
||||
|
||||
strong {
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bulk-select-all {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
display: inline-block;
|
||||
margin: 0 5px 0 0;
|
||||
vertical-align: -1px;
|
||||
}
|
||||
.btn, .btn-Action {
|
||||
margin-left: 8px;
|
||||
padding: 5px 10px;
|
||||
font-size: .9em;
|
||||
}
|
||||
.bulk-delete[disabled] {
|
||||
background: #999;
|
||||
cursor: default;
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||
.btn, .btn-Action {
|
||||
margin: 7px 5px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.files.bulk-selecting .bulk-actions {
|
||||
display: block;
|
||||
}
|
||||
.files .btn-Action {
|
||||
margin-left: 8px;
|
||||
|
||||
@@ -431,6 +483,82 @@
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.file.bulk-selected {
|
||||
background: #F8F4ED;
|
||||
outline: 3px solid #77ABB8;
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.files.bulk-selecting .file {
|
||||
cursor: pointer;
|
||||
}
|
||||
.files.bulk-selecting .link-overlay {
|
||||
display: none;
|
||||
}
|
||||
.bulk-select-control {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0;
|
||||
z-index: 5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.files.bulk-selecting .bulk-select-control {
|
||||
display: block;
|
||||
}
|
||||
.bulk-select-control input[type='checkbox'] {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.bulk-select-box {
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: #fff;
|
||||
border: 2px solid #77ABB8;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
|
||||
}
|
||||
.bulk-select-box .fa {
|
||||
display: none;
|
||||
margin: 0;
|
||||
}
|
||||
.bulk-select-checkbox:checked + .bulk-select-box {
|
||||
background: #E93250;
|
||||
border-color: #B11F36;
|
||||
}
|
||||
.bulk-select-checkbox:checked + .bulk-select-box .fa {
|
||||
display: inline-block;
|
||||
}
|
||||
.delete-file-list {
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
margin: 10px 0 0;
|
||||
padding-left: 18px;
|
||||
|
||||
li {
|
||||
font-size: .9em;
|
||||
margin-bottom: 4px;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
.html-thumbnail {
|
||||
font-size: 11px;
|
||||
margin-top: 5px;
|
||||
@@ -553,12 +681,6 @@
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-top: 5px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
.html-thumbnail, .misc-icon {
|
||||
margin: 0;
|
||||
@@ -631,6 +753,72 @@
|
||||
.files.list-view .list {
|
||||
@include dashboard-list-view;
|
||||
}
|
||||
.files.bulk-selecting .list .file > .overlay,
|
||||
.files.bulk-selecting .list .html-thumbnail > .overlay {
|
||||
display: none;
|
||||
}
|
||||
.files.list-view .file.bulk-selected {
|
||||
background: #F8F4ED;
|
||||
outline: 0;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: inset 4px 0 0 #77ABB8;
|
||||
-moz-box-shadow: inset 4px 0 0 #77ABB8;
|
||||
box-shadow: inset 4px 0 0 #77ABB8;
|
||||
}
|
||||
.files.list-view.bulk-selecting .file {
|
||||
padding-left: 48px;
|
||||
}
|
||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||
.files.bulk-selecting .file {
|
||||
padding-left: 48px;
|
||||
}
|
||||
}
|
||||
.files.list-view.bulk-selecting .bulk-select-control {
|
||||
left: 20px;
|
||||
top: 12px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||
.files.bulk-selecting .bulk-select-control {
|
||||
left: 20px;
|
||||
top: 12px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
.files.list-view.bulk-selecting .bulk-select-control input[type='checkbox'],
|
||||
.files.list-view.bulk-selecting .bulk-select-box {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.files.list-view.bulk-selecting .bulk-select-box {
|
||||
line-height: 16px;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.files.list-view.bulk-selecting .bulk-select-box .fa {
|
||||
font-size: 11px;
|
||||
}
|
||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||
.files.bulk-selecting .bulk-select-control input[type='checkbox'],
|
||||
.files.bulk-selecting .bulk-select-box {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.files.bulk-selecting .bulk-select-box {
|
||||
line-height: 16px;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.files.bulk-selecting .bulk-select-box .fa {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
.site-actions {
|
||||
float: left;
|
||||
margin-top: 20px;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
require_relative './environment.rb'
|
||||
require 'rack/test'
|
||||
|
||||
describe 'dashboard' do
|
||||
describe 'create directory' do
|
||||
@@ -63,6 +64,33 @@ describe 'dashboard' do
|
||||
_(page).must_have_content(/#{Regexp.escape(random)}\.html/)
|
||||
_(File.exist?(@site.files_path("#{random}.html"))).must_equal true
|
||||
end
|
||||
|
||||
it 'deletes multiple files through the API from the dashboard' do
|
||||
Capybara.default_driver = :selenium_chrome_headless_largewindow
|
||||
@site.store_files [
|
||||
{filename: 'bulk-one.txt', tempfile: Rack::Test::UploadedFile.new('./tests/files/text-file', 'text/plain')},
|
||||
{filename: 'bulk-two.txt', tempfile: Rack::Test::UploadedFile.new('./tests/files/text-file', 'text/plain')}
|
||||
]
|
||||
|
||||
page.set_rack_session id: @site.id
|
||||
visit '/dashboard'
|
||||
|
||||
click_button 'Select'
|
||||
find('.bulk-select-control[title="Select bulk-one.txt"]').click
|
||||
find('.bulk-select-control[title="Select bulk-two.txt"]').click
|
||||
click_button 'Delete selected'
|
||||
|
||||
_(page).must_have_css('#deleteConfirmModal', visible: true)
|
||||
within '#deleteConfirmModal' do
|
||||
click_button 'Delete'
|
||||
end
|
||||
|
||||
_(page).must_have_content('2 items have been deleted.')
|
||||
_(page).wont_have_content('bulk-one.txt')
|
||||
_(page).wont_have_content('bulk-two.txt')
|
||||
_(File.exist?(@site.files_path('bulk-one.txt'))).must_equal false
|
||||
_(File.exist?(@site.files_path('bulk-two.txt'))).must_equal false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+44
-2
@@ -236,6 +236,17 @@ describe 'api' do
|
||||
_(res[:error_type]).must_equal 'missing_filenames'
|
||||
end
|
||||
|
||||
it 'rejects a non-array filenames argument' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@site.store_files [{filename: 'deletable.txt', tempfile: Rack::Test::UploadedFile.new('./tests/files/text-file', 'text/plain')}]
|
||||
|
||||
post '/api/delete', filenames: 'deletable.txt'
|
||||
_(last_response.status).must_equal 400
|
||||
_(res[:error_type]).must_equal 'bad_filename'
|
||||
_(site_file_exists?('deletable.txt')).must_equal true
|
||||
end
|
||||
|
||||
it 'fails to delete index.html' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@@ -243,6 +254,23 @@ describe 'api' do
|
||||
_(res[:error_type]).must_equal 'cannot_delete_index'
|
||||
end
|
||||
|
||||
it 'rejects unsafe delete paths before scrubbing' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@site.store_files [{filename: 'deletable.txt', tempfile: Rack::Test::UploadedFile.new('./tests/files/text-file', 'text/plain')}]
|
||||
|
||||
post '/api/delete', filenames: ['../index.html']
|
||||
_(res[:error_type]).must_equal 'bad_filename'
|
||||
_(site_file_exists?('index.html')).must_equal true
|
||||
|
||||
post '/api/delete', filenames: ['../deletable.txt']
|
||||
_(res[:error_type]).must_equal 'bad_filename'
|
||||
_(site_file_exists?('deletable.txt')).must_equal true
|
||||
|
||||
post '/api/delete', filenames: ['bad\path.txt']
|
||||
_(res[:error_type]).must_equal 'bad_filename'
|
||||
end
|
||||
|
||||
it 'succeeds with weird filenames' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@@ -279,10 +307,10 @@ describe 'api' do
|
||||
basic_authorize @user, @pass
|
||||
post '/api/delete', filenames: ["../#{@other_site.username}"]
|
||||
_(File.exist?(@other_site.base_files_path)).must_equal true
|
||||
_(res[:error_type]).must_equal 'missing_files'
|
||||
_(res[:error_type]).must_equal 'bad_filename'
|
||||
post '/api/delete', filenames: ["../#{@other_site.username}/index.html"]
|
||||
_(File.exist?(@other_site.base_files_path+'/index.html')).must_equal true
|
||||
_(res[:error_type]).must_equal 'missing_files'
|
||||
_(res[:error_type]).must_equal 'bad_filename'
|
||||
end
|
||||
|
||||
it 'succeeds with valid filenames' do
|
||||
@@ -295,6 +323,20 @@ describe 'api' do
|
||||
_(site_file_exists?('test.jpg')).must_equal false
|
||||
_(site_file_exists?('test2.jpg')).must_equal false
|
||||
end
|
||||
|
||||
it 'succeeds with valid user session' do
|
||||
create_site
|
||||
@site.store_files [{filename: 'test.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||
@site.store_files [{filename: 'test2.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||
|
||||
post '/api/delete',
|
||||
{filenames: ['test.jpg', 'test2.jpg'], csrf_token: 'abcd'},
|
||||
{'rack.session' => {'id' => @site.id, '_csrf_token' => 'abcd'}}
|
||||
|
||||
_(res[:result]).must_equal 'success'
|
||||
_(site_file_exists?('test.jpg')).must_equal false
|
||||
_(site_file_exists?('test2.jpg')).must_equal false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create_directory' do
|
||||
|
||||
@@ -13,8 +13,8 @@ describe 'site_files' do
|
||||
post '/api/upload', hash.merge(csrf_token: 'abcd'), {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
|
||||
end
|
||||
|
||||
def delete_file(hash)
|
||||
post '/site_files/delete', hash.merge(csrf_token: 'abcd'), {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
|
||||
def delete_file(filename)
|
||||
@site.delete_file filename
|
||||
end
|
||||
|
||||
before do
|
||||
@@ -250,7 +250,7 @@ describe 'site_files' do
|
||||
_(@site.actual_space_used).must_equal @site.space_used
|
||||
file_path = @site.files_path 'test.jpg'
|
||||
_(File.exists?(file_path)).must_equal true
|
||||
delete_file filename: 'test.jpg'
|
||||
delete_file 'test.jpg'
|
||||
|
||||
_(File.exists?(file_path)).must_equal false
|
||||
_(SiteFile[site_id: @site.id, path: 'test.jpg']).must_be_nil
|
||||
@@ -264,14 +264,14 @@ describe 'site_files' do
|
||||
|
||||
it 'property deletes directories with regexp special chars in them' do
|
||||
upload '8)/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
delete_file filename: '8)'
|
||||
delete_file '8)'
|
||||
_(@site.reload.site_files.select {|f| f.path =~ /#{Regexp.quote '8)'}/}.length).must_equal 0
|
||||
end
|
||||
|
||||
it 'deletes with escaped apostrophe' do
|
||||
upload "test'ing/test.jpg" => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
_(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 1
|
||||
delete_file filename: "test'ing"
|
||||
delete_file "test'ing"
|
||||
_(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 0
|
||||
end
|
||||
|
||||
@@ -280,7 +280,7 @@ describe 'site_files' do
|
||||
upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
|
||||
space_used = @site.reload.space_used
|
||||
delete_file filename: 'test'
|
||||
delete_file 'test'
|
||||
|
||||
_(@site.reload.space_used).must_equal(space_used - File.size('./tests/files/test.jpg'))
|
||||
|
||||
@@ -298,7 +298,7 @@ describe 'site_files' do
|
||||
_(@site.site_files.select {|f| f.path == path}.length).must_equal 1
|
||||
end
|
||||
|
||||
delete_file filename: 'derp'
|
||||
delete_file 'derp'
|
||||
|
||||
@site.reload
|
||||
|
||||
@@ -307,16 +307,6 @@ describe 'site_files' do
|
||||
end
|
||||
end
|
||||
|
||||
it 'goes back to deleting directory' do
|
||||
upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
delete_file filename: 'test/test.jpg'
|
||||
_(last_response.headers['Location']).must_equal "http://example.org/dashboard?dir=test"
|
||||
|
||||
upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
delete_file filename: 'test.jpg'
|
||||
_(last_response.headers['Location']).must_equal "http://example.org/dashboard"
|
||||
end
|
||||
|
||||
it 'deletes complex nested directory structure correctly' do
|
||||
upload 'complex/level1/level2/file1.txt' => Rack::Test::UploadedFile.new('./tests/files/text-file', 'text/plain')
|
||||
upload 'complex/level1/level2/file2.txt' => Rack::Test::UploadedFile.new('./tests/files/text-file', 'text/plain')
|
||||
|
||||
@@ -34,10 +34,20 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="button" id="selectFilesButton" class="btn-Action select-files-button" onclick="toggleBulkSelect(event)"><i class="fa fa-check-square-o"></i> Select</button>
|
||||
<a href="#createFile" class="btn-Action" data-toggle="modal"><i class="fa fa-file"></i> New File</a>
|
||||
<a href="#createDir" class="btn-Action" data-toggle="modal"><i class="fa fa-folder"></i> New Folder</a>
|
||||
<a href="#" id="uploadButton" class="btn-Action"><i class="fa fa-arrow-circle-up"></i> Upload</a>
|
||||
</div>
|
||||
<div id="bulkActions" class="bulk-actions">
|
||||
<label class="bulk-select-all">
|
||||
<input type="checkbox" id="bulkSelectAll" onchange="toggleSelectAllFiles(this.checked)">
|
||||
<span>Select all</span>
|
||||
</label>
|
||||
<strong><span id="selectedFileCount">0</span> selected</strong>
|
||||
<button type="button" class="btn bulk-clear" onclick="clearFileSelection()">Clear</button>
|
||||
<button type="button" class="btn-Action btn-danger bulk-delete" id="bulkDeleteButton" onclick="confirmBulkDelete()" disabled><i class="fa fa-trash"></i> Delete selected</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<form action="/site_files/upload" class="dropzone" id="uploads">
|
||||
@@ -46,6 +56,7 @@
|
||||
<input name="dir" type="hidden" value="<%= @dir %>" id="dir">
|
||||
<div class="upload-Boundary with-instruction">
|
||||
<% @file_list.each do |file| %>
|
||||
<% file_dom_id = Digest::SHA256.hexdigest file[:path] %>
|
||||
<div class="file filehover">
|
||||
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
|
||||
<div class="html-thumbnail html fileimagehover">
|
||||
@@ -69,6 +80,13 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if !file[:is_root_index] %>
|
||||
<label class="bulk-select-control" title="Select <%= file[:path] %>">
|
||||
<input type="checkbox" class="bulk-select-checkbox" value="<%= file[:path] %>" aria-label="Select <%= file[:path] %>">
|
||||
<span class="bulk-select-box"><i class="fa fa-check"></i></span>
|
||||
</label>
|
||||
<% end %>
|
||||
|
||||
<a class="title">
|
||||
<%= file[:name] %>
|
||||
</a>
|
||||
@@ -84,7 +102,7 @@
|
||||
</div>
|
||||
|
||||
<div class="overlay">
|
||||
<div id="<%= Digest::SHA256.hexdigest file[:path] %>" style="display: none"><%= file[:path] %></div>
|
||||
<div id="<%= file_dom_id %>" style="display: none"><%= file[:path] %></div>
|
||||
<% if file[:is_editable] && !file[:is_directory] %>
|
||||
<a href="/site_files/text_editor?filename=<%= Rack::Utils.escape file[:path] %>"><i class="fa fa-edit" title="Edit"></i> Edit</a>
|
||||
<% end %>
|
||||
@@ -92,8 +110,8 @@
|
||||
<a href="?dir=<%= Rack::Utils.escape file[:path] %>"><i class="fa fa-edit" title="Manage"></i> Manage</a>
|
||||
<% end %>
|
||||
<% if !file[:is_root_index] %>
|
||||
<a href="#" onclick="confirmFileRename($('#<%= Digest::SHA256.hexdigest file[:path] %>').text())"><i class="fa fa-file" title="Rename"></i> Rename</a>
|
||||
<a href="#" onclick="confirmFileDelete($('#<%= Digest::SHA256.hexdigest file[:path] %>').text())"><i class="fa fa-trash" title="Delete"></i> Delete</a>
|
||||
<a href="#" onclick="confirmFileRename($('#<%= file_dom_id %>').text())"><i class="fa fa-file" title="Rename"></i> Rename</a>
|
||||
<a href="#" onclick="confirmFileDelete($('#<%= file_dom_id %>').text())"><i class="fa fa-trash" title="Delete"></i> Delete</a>
|
||||
<% end %>
|
||||
<% if file[:is_directory] %>
|
||||
<a class="link-overlay" href="?dir=<%= Rack::Utils.escape file[:path] %>" title="View <%= file[:path] %>"></a>
|
||||
@@ -105,4 +123,4 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,10 +75,7 @@
|
||||
<%== erb :'dashboard/files' %>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/site_files/delete" id="deleteFilenameForm">
|
||||
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||
<input type="hidden" id="deleteFilenameInput" name="filename">
|
||||
</form>
|
||||
<input id="deleteCSRFToken" type="hidden" value="<%= csrf_token %>">
|
||||
|
||||
<div class="modal hide" id="deleteConfirmModal" tabindex="-1" role="dialog" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
@@ -86,7 +83,9 @@
|
||||
<h3 id="deleteConfirmModalLabel">Confirm deletion</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete <strong><span id="deleteFileName"></span></strong>. Are you sure?</p>
|
||||
<p id="deleteSingleMessage">You are about to delete <strong><span id="deleteFileName"></span></strong>. Are you sure?</p>
|
||||
<p id="deleteMultipleMessage" style="display: none">You are about to delete <strong><span id="deleteFileCount">0</span> items</strong>. Are you sure?</p>
|
||||
<ul id="deleteFileList" class="delete-file-list"></ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn cancel" data-dismiss="modal" aria-hidden="true" type="button">Cancel</button>
|
||||
|
||||
Reference in New Issue
Block a user