Merge pull request #497 from neocities/fileuploadrefactor

File Upload Refactor
This commit is contained in:
Kyle Drake 2024-03-11 11:44:12 -05:00 committed by GitHub
commit d506f6cb3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 704 additions and 647 deletions

View file

@ -42,13 +42,33 @@ get '/api/list' do
end end
def extract_files(params, files = []) def extract_files(params, files = [])
params.each do |key, value| # Check if the entire input is directly an array of files
# If the value is a Hash and contains a :tempfile key, it's considered an uploaded file. if params.is_a?(Array)
if value.is_a?(Hash) && value.has_key?(:tempfile) && !value[:tempfile].nil? params.each do |item|
files << {filename: value[:name], tempfile: value[:tempfile]} # Call extract_files on each item if it's an Array or Hash to handle nested structures
elsif value.is_a?(Hash) || value.is_a?(Array) if item.is_a?(Array) || item.is_a?(Hash)
# If the value is a Hash or Array, recursively search for more files. extract_files(item, files)
extract_files(value, files) end
end
elsif params.is_a?(Hash)
params.each do |key, value|
# If the value is a Hash and contains a :tempfile key, it's considered an uploaded file.
if value.is_a?(Hash) && value.has_key?(:tempfile) && !value[:tempfile].nil?
files << {filename: value[:name], tempfile: value[:tempfile]}
elsif value.is_a?(Array)
value.each do |val|
if val.is_a?(Hash) && val.has_key?(:tempfile) && !val[:tempfile].nil?
# Directly add the file info if it's an uploaded file within an array
files << {filename: val[:name], tempfile: val[:tempfile]}
elsif val.is_a?(Hash) || val.is_a?(Array)
# Recursively search for more files if the element is a Hash or Array
extract_files(val, files)
end
end
elsif value.is_a?(Hash)
# Recursively search for more files if the value is a Hash
extract_files(value, files)
end
end end
end end
files files
@ -56,9 +76,22 @@ end
post '/api/upload' do post '/api/upload' do
require_api_credentials require_api_credentials
files = extract_files params files = extract_files params
if !params[:username].blank?
site = Site[username: params[:username]]
if site.nil? || site.is_deleted
api_error 400, 'site_not_found', "could not find site"
end
if site.owned_by?(current_site)
@_site = site
else
api_error 400, 'site_not_allowed', "not allowed to change this site with your current logged in site"
end
end
api_error 400, 'missing_files', 'you must provide files to upload' if files.empty? api_error 400, 'missing_files', 'you must provide files to upload' if files.empty?
uploaded_size = files.collect {|f| f[:tempfile].size}.inject{|sum,x| sum + x } uploaded_size = files.collect {|f| f[:tempfile].size}.inject{|sum,x| sum + x }
@ -68,16 +101,28 @@ post '/api/upload' do
end end
if current_site.too_many_files?(files.length) if current_site.too_many_files?(files.length)
api_error 400, 'too_many_files', "cannot exceed the maximum site files limit (#{current_site.plan_feature(:maximum_site_files)}), #{current_site.supporter? ? 'please contact support' : 'please upgrade to a supporter account'}" api_error 400, 'too_many_files', "cannot exceed the maximum site files limit (#{current_site.plan_feature(:maximum_site_files)})"
end end
files.each do |file| files.each do |file|
if !current_site.okay_to_upload?(file) if !current_site.okay_to_upload?(file)
api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type (or contains not allowed content) for this site, files have not been uploaded" api_error 400, 'invalid_file_type', "#{file[:filename]} is not a allowed file type for free sites, supporter required"
end end
if File.directory? file[:filename] if File.directory? file[:filename]
api_error 400, 'directory_exists', 'this name is being used by a directory, cannot continue' api_error 400, 'directory_exists', "#{file[:filename]} being used by a directory"
end
if current_site.file_size_too_large? file[:tempfile].size
api_error 400, 'file_too_large' "#{file[:filename]} is too large"
end
if SiteFile.path_too_long? file[:filename]
api_error 400, 'file_path_too_long', "#{file[:filename]} path is too long"
end
if SiteFile.name_too_long? file[:filename]
api_error 400, 'file_name_too_long', "#{file[:filename]} filename is too long"
end end
end end
@ -191,7 +236,7 @@ post '/api/:name' do
end end
def require_api_credentials def require_api_credentials
return true if current_site return true if current_site && csrf_safe?
if !request.env['HTTP_AUTHORIZATION'].nil? if !request.env['HTTP_AUTHORIZATION'].nil?
init_api_credentials init_api_credentials

View file

@ -8,7 +8,7 @@ get '/dashboard' do
current_site.save_changes validate: false current_site.save_changes validate: false
end end
erb :'dashboard' erb :'dashboard/index'
end end
def dashboard_init def dashboard_init
@ -30,3 +30,11 @@ def dashboard_init
@dir = params[:dir] @dir = params[:dir]
@file_list = current_site.file_list @dir @file_list = current_site.file_list @dir
end end
get '/dashboard/files' do
require_login
dashboard_init
dont_browser_cache
erb :'dashboard/files', layout: false
end

View file

@ -75,7 +75,9 @@ post '/site_files/create' do
end end
def file_upload_response(error=nil) def file_upload_response(error=nil)
flash[:error] = error if error if error
flash[:error] = error
end
if params[:from_button] if params[:from_button]
query_string = params[:dir] ? "?"+Rack::Utils.build_query(dir: params[:dir]) : '' query_string = params[:dir] ? "?"+Rack::Utils.build_query(dir: params[:dir]) : ''
@ -90,77 +92,6 @@ def require_login_file_upload_ajax
file_upload_response 'You are not signed in!' unless signed_in? file_upload_response 'You are not signed in!' unless signed_in?
end end
post '/site_files/upload' do
if params[:filename]
require_login_file_upload_ajax
tempfile = Tempfile.new 'neocities_saving_file'
input = request.body.read
tempfile.set_encoding input.encoding
tempfile.write input
tempfile.close
params[:files] = [{filename: params[:filename], tempfile: tempfile}]
else
require_login
end
@errors = []
if params[:files].nil?
file_upload_response "Uploaded files were not seen by the server, cancelled. We don't know what's causing this yet. Please contact us so we can help fix it. Thanks!"
end
# For migration from original design.. some pages out there won't have the site_id param yet for a while.
site = params[:site_id].nil? ? current_site : Site[params[:site_id]]
unless site.owned_by?(current_site)
file_upload_response 'You do not have permission to save this file. Did you sign in as a different user?'
end
params[:files].each_with_index do |file,i|
dir_name = ''
dir_name = params[:dir] if params[:dir]
unless params[:file_paths].nil? || params[:file_paths].empty? || params[:file_paths].length == 0
file_path = params[:file_paths][i]
unless file_path.nil?
dir_name += '/' + Pathname(file_path).dirname.to_s
end
end
file_base_name = site.scrubbed_path file[:filename].force_encoding('UTF-8')
file[:filename] = "#{dir_name.force_encoding('UTF-8')}/#{file_base_name}"
if current_site.file_size_too_large? file[:tempfile].size
file_upload_response "#{Rack::Utils.escape_html file[:filename]} is too large, upload cancelled."
end
if !site.okay_to_upload? file
file_upload_response %{#{Rack::Utils.escape_html file[:filename]}: file type (or content in file) is only supported by <a href="/supporter">supporter accounts</a>. <a href="/site_files/allowed_types">Why We Do This</a>}
end
if SiteFile.path_too_long? file[:filename]
file_upload_response "#{Rack::Utils.escape_html file[:filename]}: path is too long, upload cancelled."
end
if SiteFile.name_too_long? file_base_name
file_upload_response "#{Rack::Utils.escape_html file[:filename]}: file name is too long, upload cancelled."
end
end
uploaded_size = params[:files].collect {|f| f[:tempfile].size}.inject{|sum,x| sum + x }
if site.file_size_too_large? uploaded_size
file_upload_response "File(s) do not fit in your available free space, upload cancelled."
end
if site.too_many_files? params[:files].length
file_upload_response "Your site has exceeded the maximum number of files, please delete some files first."
end
results = site.store_files params[:files]
file_upload_response
end
post '/site_files/delete' do post '/site_files/delete' do
require_login require_login
path = HTMLEntities.new.decode params[:filename] path = HTMLEntities.new.decode params[:filename]
@ -246,7 +177,16 @@ get %r{\/site_files\/text_editor\/(.+)} do
dont_browser_cache dont_browser_cache
@filename = params[:captures].first @filename = params[:captures].first
redirect '/site_files/text_editor?filename=' + Rack::Utils.escape(@filename)
end
get '/site_files/text_editor' do
require_login
dont_browser_cache
@filename = params[:filename]
extname = File.extname @filename extname = File.extname @filename
@ace_mode = case extname @ace_mode = case extname
when /htm|html/ then 'html' when /htm|html/ then 'html'
when /js/ then 'javascript' when /js/ then 'javascript'
@ -286,4 +226,4 @@ end
get '/site_files/mount_info' do get '/site_files/mount_info' do
@title = 'Site Mount Information' @title = 'Site Mount Information'
erb :'site_files/mount_info' erb :'site_files/mount_info'
end end

View file

@ -131,3 +131,15 @@ def hcaptcha_valid?
false false
end end
end end
JS_ESCAPE_MAP = {"\\" => "\\\\", "</" => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'", "`" => "\\`", "$" => "\\$"}
def escape_javascript(javascript)
javascript = javascript.to_s
if javascript.empty?
result = ""
else
result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
end
result
end

View file

@ -84,7 +84,7 @@ class Site < Sequel::Model
SCREENSHOT_RESOLUTIONS = ['540x405', '210x158', '100x100', '50x50'] SCREENSHOT_RESOLUTIONS = ['540x405', '210x158', '100x100', '50x50']
THUMBNAIL_RESOLUTIONS = ['210x158'] THUMBNAIL_RESOLUTIONS = ['210x158']
MAX_FILE_SIZE = 10**8 # 100 MB MAX_FILE_SIZE = 10**8 # 100 MB, change dashboard.js dropzone file size limit if you change this
CLAMAV_THREAT_MATCHES = [ CLAMAV_THREAT_MATCHES = [
/^VBS/, /^VBS/,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

58
public/img/drag-drop.svg Normal file
View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="67.754738mm"
height="3.4416525mm"
viewBox="0 0 67.754738 3.4416525"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="drag-drop.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="3.1108586"
inkscape:cx="71.523663"
inkscape:cy="-55.772384"
inkscape:window-width="2490"
inkscape:window-height="1376"
inkscape:window-x="70"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-23.11286,-40.759304)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="22.954386"
y="43.43959"
id="text82970"><tspan
sodipodi:role="line"
id="tspan82968"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#f6f0e6;fill-opacity:1;stroke-width:0.264583"
x="22.954386"
y="43.43959">drag and drop files here to upload</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

157
public/js/dashboard.js Normal file
View file

@ -0,0 +1,157 @@
if(localStorage && localStorage.getItem('viewType') == 'list')
$('#filesDisplay').addClass('list-view')
function confirmFileRename(path) {
$('#renamePathInput').val(path);
$('#renameNewPathInput').val(path);
$('#renameModal').modal();
}
function confirmFileDelete(name) {
$('#deleteFileName').text(name);
$('#deleteConfirmModal').modal();
}
function fileDelete() {
$('#deleteFilenameInput').val($('#deleteFileName').html());
$('#deleteFilenameForm').submit();
}
function clickUploadFiles() {
$("input[id='uploadFiles']").click()
}
function showUploadProgress() {
$('#uploadingOverlay').css('display', 'block')
}
function hideUploadProgress() {
$('#progressBar').css('display', 'none')
$('#uploadingOverlay').css('display', 'none')
}
$('#createDir').on('shown', function () {
$('#newDirInput').focus();
})
$('#createFile').on('shown', function () {
$('#newFileInput').focus();
})
function listView() {
if(localStorage)
localStorage.setItem('viewType', 'list')
$('#filesDisplay').addClass('list-view')
}
function iconView() {
if(localStorage)
localStorage.removeItem('viewType')
$('#filesDisplay').removeClass('list-view')
}
function alertAdd(text) {
var a = $('#alertDialogue');
a.css('display', 'block');
a.append(text+'<br>');
}
function alertClear(){
var a = $('#alertDialogue');
a.css('display', 'none');
a.text('');
}
function alertType(type){
var a = $('#alertDialogue');
a.removeClass('alert-success');
a.removeClass('alert-error');
a.addClass('alert-'+type);
}
var processedFiles = 0;
var uploadedFiles = 0;
var uploadedFileErrors = 0;
function joinPaths(...paths) {
return paths
.map(path => path.replace(/(^\/|\/$)/g, ''))
.filter(path => path !== '')
.join('/');
}
function reInitDashboardFiles() {
new Dropzone("#uploads", {
url: "/api/upload",
paramName: 'file',
dictDefaultMessage: "",
uploadMultiple: false,
parallelUploads: 1,
maxFilesize: 104857600, // 100MB
clickable: document.getElementById('uploadButton'),
init: function() {
this.on("processing", function(file) {
var dir = $('#uploads input[name="dir"]').val();
if(file.fullPath) {
this.options.paramName = joinPaths(dir,file.fullPath);
} else {
this.options.paramName = joinPaths(dir, file.name);
}
processedFiles++;
$('#uploadFileName').text(this.options.paramName).prepend('<i class="icon-file"></i> ');
});
this.on("success", function(file) {
uploadedFiles++;
});
this.on("error", function(file, message) {
uploadedFiles++;
uploadedFileErrors++;
alertType('error');
if (message && message.message) {
alertAdd(message.message);
} else {
alertAdd(this.options.paramName+' failed to upload');
}
});
this.on("queuecomplete", function() {
hideUploadProgress();
if(uploadedFileErrors > 0) {
alertType('error');
alertAdd(uploadedFiles-uploadedFileErrors+'/'+uploadedFiles+' files uploaded successfully');
} else {
alertType('success');
alertAdd(uploadedFiles+' files uploaded successfully');
}
reloadDashboardFiles();
});
this.on("addedfiles", function(files) {
uploadedFiles = 0;
uploadedFileErrors = 0;
alertClear();
showUploadProgress();
});
}
});
document.getElementById('uploadButton').addEventListener('click', function(event) {
event.preventDefault();
});
}
function reloadDashboardFiles() {
var dir = $('#uploads input[name="dir"]').val();
$.get('/dashboard/files?dir='+encodeURIComponent(dir), function(data) {
$('#filesDisplay').html(data);
reInitDashboardFiles();
});
}
// for first time load
reInitDashboardFiles();

2
public/js/dropzone-min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
/*
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);
if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

View file

@ -60,7 +60,7 @@ a{
@import 'project-Footer'; // Project Specific Footer Styling @import 'project-Footer'; // Project Specific Footer Styling
.alert-error { .alert-error {
background-color:#F5BA00; color:#fff; background-color:#f39c12; color:#fff;
} }
.modal { .modal {
@ -89,4 +89,4 @@ a{
border: 0; border: 0;
background: none; background: none;
padding: 0 23px 19px 0; padding: 0 23px 19px 0;
} }

View file

@ -352,7 +352,7 @@
min-height: 300px; min-height: 300px;
} }
.files .list .upload-Boundary.with-instruction { .files .list .upload-Boundary.with-instruction {
background: url(/img/drag-drop.png) no-repeat center center; background: url(/img/drag-drop.svg) no-repeat center center;
@media (max-device-width:480px), screen and (max-width:800px) { @media (max-device-width:480px), screen and (max-width:800px) {
background: 0; background: 0;
@ -370,7 +370,7 @@
font-style: italic; font-style: italic;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
width: 400px; width: 90%;
margin-top: 14%; margin-top: 14%;
padding: 25px 40px 28px 40px; padding: 25px 40px 28px 40px;
-webkit-box-shadow: 1px 1px 21px 5px rgba(50, 50, 50, 0.5); -webkit-box-shadow: 1px 1px 21px 5px rgba(50, 50, 50, 0.5);

View file

@ -308,6 +308,18 @@ describe 'api' do
_(site_file_exists?('test.jpg')).must_equal true _(site_file_exists?('test.jpg')).must_equal true
end end
it 'fails api_key auth unless controls site' do
create_site
@site.generate_api_key!
@other_site = Fabricate :site
header 'Authorization', "Bearer #{@site.api_key}"
post '/api/upload', 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'), 'username' => @other_site.username
_(res[:result]).must_equal 'error'
_(@other_site.site_files.select {|s| s.path == 'test.jpg'}).must_equal []
_(res[:error_type]).must_equal 'site_not_allowed'
end
it 'succeeds with square bracket in filename' do it 'succeeds with square bracket in filename' do
create_site create_site
@site.generate_api_key! @site.generate_api_key!
@ -329,6 +341,34 @@ describe 'api' do
_(site_file_exists?('test.jpg')).must_equal true _(site_file_exists?('test.jpg')).must_equal true
end end
it 'succeeds with valid user session controlled site' do
create_site
@other_site = Fabricate :site, parent_site_id: @site.id
post '/api/upload',
{'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
'csrf_token' => 'abcd',
'username' => @other_site.username},
{'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
_(res[:result]).must_equal 'success'
_(last_response.status).must_equal 200
_(@other_site.site_files.select {|sf| sf.path == 'test.jpg'}.length).must_equal 1
end
it 'fails session upload unless controls site' do
create_site
@other_site = Fabricate :site
post '/api/upload', {
'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
'username' => @other_site.username,
'csrf_token' => 'abcd'},
{'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
_(res[:result]).must_equal 'error'
_(@other_site.site_files.select {|s| s.path == 'test.jpg'}).must_equal []
_(res[:error_type]).must_equal 'site_not_allowed'
end
it 'fails with bad api key' do it 'fails with bad api key' do
create_site create_site
@site.generate_api_key! @site.generate_api_key!

View file

@ -9,7 +9,7 @@ describe 'site_files' do
end end
def upload(hash) def upload(hash)
post '/site_files/upload', hash.merge(csrf_token: 'abcd'), {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }} post '/api/upload', hash.merge(csrf_token: 'abcd'), {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
end end
def delete_file(hash) def delete_file(hash)
@ -45,7 +45,7 @@ describe 'site_files' do
it 'works with html file' do it 'works with html file' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/notindex.html', 'text/html') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/notindex.html', 'text/html')
upload 'files[]' => uploaded_file upload 'notindex.html' => uploaded_file
PurgeCacheWorker.jobs.clear PurgeCacheWorker.jobs.clear
testfile = @site.site_files_dataset.where(path: 'notindex.html').first testfile = @site.site_files_dataset.where(path: 'notindex.html').first
testfile.rename 'notindex2.html' testfile.rename 'notindex2.html'
@ -55,7 +55,7 @@ describe 'site_files' do
it 'renames in same path' do it 'renames in same path' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'test.jpg' => uploaded_file
testfile = @site.site_files_dataset.where(path: 'test.jpg').first testfile = @site.site_files_dataset.where(path: 'test.jpg').first
_(testfile).wont_equal nil _(testfile).wont_equal nil
@ -66,9 +66,6 @@ describe 'site_files' do
end end
it 'fails when file does not exist' do it 'fails when file does not exist' do
#uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
#upload 'files[]' => uploaded_file
post '/site_files/rename', {path: 'derp.jpg', new_path: 'derp2.jpg', csrf_token: 'abcd'}, {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }} post '/site_files/rename', {path: 'derp.jpg', new_path: 'derp2.jpg', csrf_token: 'abcd'}, {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
_(last_response.headers['Location']).must_match /dashboard/ _(last_response.headers['Location']).must_match /dashboard/
get '/dashboard', {}, {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }} get '/dashboard', {}, {'rack.session' => { 'id' => @site.id, '_csrf_token' => 'abcd' }}
@ -77,7 +74,7 @@ describe 'site_files' do
it 'fails for bad extension change' do it 'fails for bad extension change' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'test.jpg' => uploaded_file
testfile = @site.site_files_dataset.where(path: 'test.jpg').first testfile = @site.site_files_dataset.where(path: 'test.jpg').first
res = testfile.rename('dasharezone.exe') res = testfile.rename('dasharezone.exe')
@ -89,7 +86,7 @@ describe 'site_files' do
no_file_restriction_plans = Site::PLAN_FEATURES.select {|p,v| v[:no_file_restrictions] == true} no_file_restriction_plans = Site::PLAN_FEATURES.select {|p,v| v[:no_file_restrictions] == true}
no_file_restriction_plans.each do |plan_type,hash| no_file_restriction_plans.each do |plan_type,hash|
@site = Fabricate :site, plan_type: plan_type @site = Fabricate :site, plan_type: plan_type
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav') upload 'flowercrime.wav' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav')
testfile = @site.site_files_dataset.where(path: 'flowercrime.wav').first testfile = @site.site_files_dataset.where(path: 'flowercrime.wav').first
res = testfile.rename('flowercrime.exe') res = testfile.rename('flowercrime.exe')
_(res.first).must_equal true _(res.first).must_equal true
@ -126,18 +123,13 @@ describe 'site_files' do
end end
it 'changes path of files and dirs within directory when changed' do it 'changes path of files and dirs within directory when changed' do
upload( upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'test', upload 'test/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
upload(
'dir' => 'test',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
)
PurgeCacheWorker.jobs.clear PurgeCacheWorker.jobs.clear
@site.site_files.select {|s| s.path == 'test'}.first.rename('test2') @site.site_files.select {|s| s.path == 'test'}.first.rename('test2')
_(@site.site_files.select {|sf| sf.path =~ /test2\/index.html/}.length).must_equal 1 _(@site.site_files.select {|sf| sf.path =~ /test2\/index.html/}.length).must_equal 1
_(@site.site_files.select {|sf| sf.path =~ /test2\/test.jpg/}.length).must_equal 1 _(@site.site_files.select {|sf| sf.path =~ /test2\/test.jpg/}.length).must_equal 1
_(@site.site_files.select {|sf| sf.path =~ /test\/test.jpg/}.length).must_equal 0 _(@site.site_files.select {|sf| sf.path =~ /test\/test.jpg/}.length).must_equal 0
@ -146,14 +138,8 @@ describe 'site_files' do
end end
it 'doesnt wipe out existing file' do it 'doesnt wipe out existing file' do
upload( upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'test', upload 'test/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
upload(
'dir' => 'test',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
)
res = @site.site_files_dataset.where(path: 'test/index.html').first.rename('test/test.jpg') res = @site.site_files_dataset.where(path: 'test/index.html').first.rename('test/test.jpg')
_(res).must_equal [false, 'file already exists'] _(res).must_equal [false, 'file already exists']
@ -172,15 +158,14 @@ describe 'site_files' do
end end
it 'works with unicode characters' do it 'works with unicode characters' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file
@site.site_files_dataset.where(path: 'test.jpg').first.rename("HELL💩؋.jpg") @site.site_files_dataset.where(path: 'test.jpg').first.rename("HELL💩؋.jpg")
_(@site.site_files_dataset.where(path: "HELL💩؋.jpg").first).wont_equal nil _(@site.site_files_dataset.where(path: "HELL💩؋.jpg").first).wont_equal nil
end end
it 'scrubs weird carriage return shit characters' do it 'scrubs weird carriage return shit characters' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
_(proc { _(proc {
@site.site_files_dataset.where(path: 'test.jpg').first.rename("\r\n\t.jpg") @site.site_files_dataset.where(path: 'test.jpg').first.rename("\r\n\t.jpg")
}).must_raise ArgumentError }).must_raise ArgumentError
@ -196,7 +181,7 @@ describe 'site_files' do
it 'works' do it 'works' do
initial_space_used = @site.space_used initial_space_used = @site.space_used
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'test.jpg' => uploaded_file
PurgeCacheWorker.jobs.clear PurgeCacheWorker.jobs.clear
@ -217,30 +202,21 @@ describe 'site_files' do
end end
it 'property deletes directories with regexp special chars in them' do it 'property deletes directories with regexp special chars in them' do
upload 'dir' => '8)', 'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') upload '8)/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
delete_file filename: '8)' delete_file filename: '8)'
_(@site.reload.site_files.select {|f| f.path =~ /#{Regexp.quote '8)'}/}.length).must_equal 0 _(@site.reload.site_files.select {|f| f.path =~ /#{Regexp.quote '8)'}/}.length).must_equal 0
end end
it 'deletes with escaped apostrophe' do it 'deletes with escaped apostrophe' do
upload( upload "test'ing/test.jpg" => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => "test'ing",
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
_(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 1 _(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 1
delete_file filename: "test'ing" delete_file filename: "test'ing"
_(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 0 _(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 0
end end
it 'deletes a directory and all files in it' do it 'deletes a directory and all files in it' do
upload( upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'test', upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
upload(
'dir' => '',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
space_used = @site.reload.space_used space_used = @site.reload.space_used
delete_file filename: 'test' delete_file filename: 'test'
@ -253,10 +229,7 @@ describe 'site_files' do
end end
it 'deletes records for nested directories' do it 'deletes records for nested directories' do
upload( upload 'derp/ing/tons/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'derp/ing/tons',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
expected_site_file_paths = ['derp', 'derp/ing', 'derp/ing/tons', 'derp/ing/tons/test.jpg'] expected_site_file_paths = ['derp', 'derp/ing', 'derp/ing/tons', 'derp/ing/tons/test.jpg']
@ -274,16 +247,11 @@ describe 'site_files' do
end end
it 'goes back to deleting directory' do it 'goes back to deleting directory' do
upload( upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'test',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
delete_file filename: 'test/test.jpg' delete_file filename: 'test/test.jpg'
_(last_response.headers['Location']).must_equal "http://example.org/dashboard?dir=test" _(last_response.headers['Location']).must_equal "http://example.org/dashboard?dir=test"
upload( upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
delete_file filename: 'test.jpg' delete_file filename: 'test.jpg'
_(last_response.headers['Location']).must_equal "http://example.org/dashboard" _(last_response.headers['Location']).must_equal "http://example.org/dashboard"
end end
@ -291,17 +259,17 @@ describe 'site_files' do
describe 'upload' do describe 'upload' do
it 'works with empty files' do it 'works with empty files' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/empty.js', 'text/javascript') upload 'empty.js' => Rack::Test::UploadedFile.new('./tests/files/empty.js', 'text/javascript')
_(File.exists?(@site.files_path('empty.js'))).must_equal true _(File.exists?(@site.files_path('empty.js'))).must_equal true
end end
it 'manages files with invalid UTF8' do it 'manages files with invalid UTF8' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/invalidutf8.html', 'text/html') upload 'invalidutf8.html' => Rack::Test::UploadedFile.new('./tests/files/invalidutf8.html', 'text/html')
_(File.exists?(@site.files_path('invalidutf8.html'))).must_equal true _(File.exists?(@site.files_path('invalidutf8.html'))).must_equal true
end end
it 'works with manifest files' do it 'works with manifest files' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/cache.manifest', 'text/cache-manifest') upload 'cache.manifest' => Rack::Test::UploadedFile.new('./tests/files/cache.manifest', 'text/cache-manifest')
_(File.exists?(@site.files_path('cache.manifest'))).must_equal true _(File.exists?(@site.files_path('cache.manifest'))).must_equal true
end end
@ -312,7 +280,7 @@ describe 'site_files' do
file.write("derp") file.write("derp")
end end
upload 'files[]' => Rack::Test::UploadedFile.new(file_path, 'text/html') upload file_path => Rack::Test::UploadedFile.new(file_path, 'text/html')
_(last_response.body).must_match /name is too long/i _(last_response.body).must_match /name is too long/i
ensure ensure
FileUtils.rm file_path FileUtils.rm file_path
@ -320,20 +288,17 @@ describe 'site_files' do
end end
it 'fails with path greater than limit' do it 'fails with path greater than limit' do
upload( upload "#{(("a" * 50 + "/") * (SiteFile::FILE_PATH_CHARACTER_LIMIT / 50 - 1) + "a" * 50)}/test.jpg" => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => (("a" * 50 + "/") * (SiteFile::FILE_PATH_CHARACTER_LIMIT / 50 - 1) + "a" * 50),
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
_(last_response.body).must_match /path is too long/i _(last_response.body).must_match /path is too long/i
end end
it 'works with otf fonts' do it 'works with otf fonts' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/chunkfive.otf', 'application/vnd.ms-opentype') upload 'chunkfive.otf' => Rack::Test::UploadedFile.new('./tests/files/chunkfive.otf', 'application/vnd.ms-opentype')
_(File.exists?(@site.files_path('chunkfive.otf'))).must_equal true _(File.exists?(@site.files_path('chunkfive.otf'))).must_equal true
end end
it 'purges cache for html file with extension removed' do it 'purges cache for html file with extension removed' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/notindex.html', 'text/html') upload 'notindex.html' => Rack::Test::UploadedFile.new('./tests/files/notindex.html', 'text/html')
_(PurgeCacheWorker.jobs.length).must_equal 1 _(PurgeCacheWorker.jobs.length).must_equal 1
PurgeCacheWorker.new.perform @site.username, '/notindex.html' PurgeCacheWorker.new.perform @site.username, '/notindex.html'
_(PurgeCacheWorker.jobs.first['args'].last).must_equal '/notindex' _(PurgeCacheWorker.jobs.first['args'].last).must_equal '/notindex'
@ -341,7 +306,7 @@ describe 'site_files' do
it 'succeeds with index.html file' do it 'succeeds with index.html file' do
_(@site.site_changed).must_equal false _(@site.site_changed).must_equal false
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html') upload 'index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(File.exists?(@site.files_path('index.html'))).must_equal true _(File.exists?(@site.files_path('index.html'))).must_equal true
@ -367,9 +332,9 @@ describe 'site_files' do
it 'provides the correct space used after overwriting an existing file' do it 'provides the correct space used after overwriting an existing file' do
initial_space_used = @site.space_used initial_space_used = @site.space_used
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'test.jpg' => uploaded_file
second_uploaded_file = Rack::Test::UploadedFile.new('./tests/files/img/test.jpg', 'image/jpeg') second_uploaded_file = Rack::Test::UploadedFile.new('./tests/files/img/test.jpg', 'image/jpeg')
upload 'files[]' => second_uploaded_file upload 'test.jpg' => second_uploaded_file
_(@site.reload.space_used).must_equal initial_space_used + second_uploaded_file.size _(@site.reload.space_used).must_equal initial_space_used + second_uploaded_file.size
_(@site.space_used).must_equal @site.actual_space_used _(@site.space_used).must_equal @site.actual_space_used
end end
@ -377,27 +342,22 @@ describe 'site_files' do
it 'does not change title for subdir index.html' do it 'does not change title for subdir index.html' do
title = @site.title title = @site.title
upload( upload(
'dir' => 'derpie', 'derpie/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
) )
_(@site.reload.title).must_equal title _(@site.reload.title).must_equal title
end end
it 'purges cache for /subdir/' do # (not /subdir which is just a redirect to /subdir/) it 'purges cache for /subdir/' do # (not /subdir which is just a redirect to /subdir/)
upload( upload(
'dir' => 'subdir', 'subdir/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
) )
_(PurgeCacheWorker.jobs.select {|j| j['args'].last == '/subdir/'}.length).must_equal 1 _(PurgeCacheWorker.jobs.select {|j| j['args'].last == '/subdir/'}.length).must_equal 1
end end
it 'succeeds with multiple files' do it 'succeeds with multiple files' do
upload( upload(
'file_paths' => ['one/test.jpg', 'two/test.jpg'], 'one/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
'files' => [ 'two/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
]
) )
_(@site.site_files.select {|s| s.path == 'one'}.length).must_equal 1 _(@site.site_files.select {|s| s.path == 'one'}.length).must_equal 1
@ -409,7 +369,7 @@ describe 'site_files' do
it 'succeeds with valid file' do it 'succeeds with valid file' do
initial_space_used = @site.space_used initial_space_used = @site.space_used
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'test.jpg' => uploaded_file
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(File.exists?(@site.files_path('test.jpg'))).must_equal true _(File.exists?(@site.files_path('test.jpg'))).must_equal true
@ -434,22 +394,23 @@ describe 'site_files' do
it 'works with square bracket filename' do it 'works with square bracket filename' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/te[s]t.jpg', 'image/jpeg') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/te[s]t.jpg', 'image/jpeg')
upload 'files[]' => uploaded_file upload 'te[s]t.jpg' => uploaded_file
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(File.exists?(@site.files_path('te[s]t.jpg'))).must_equal true _(File.exists?(@site.files_path('te[s]t.jpg'))).must_equal true
end end
it 'sets site changed to false if index is empty' do it 'sets site changed to false if index is empty' do
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/blankindex/index.html', 'text/html') uploaded_file = Rack::Test::UploadedFile.new('./tests/files/blankindex/index.html', 'text/html')
upload 'files[]' => uploaded_file upload 'index.html' => uploaded_file
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(@site.empty_index?).must_equal true _(@site.empty_index?).must_equal true
_(@site.site_changed).must_equal false _(@site.site_changed).must_equal false
end end
it 'fails with unsupported file' do it 'fails with unsupported file' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav') upload 'flowercrime.wav' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav')
_(last_response.body).must_match /only supported by.+supporter account/i
_(JSON.parse(last_response.body)['error_type']).must_equal 'invalid_file_type'
_(File.exists?(@site.files_path('flowercrime.wav'))).must_equal false _(File.exists?(@site.files_path('flowercrime.wav'))).must_equal false
_(@site.site_changed).must_equal false _(@site.site_changed).must_equal false
end end
@ -458,17 +419,17 @@ describe 'site_files' do
no_file_restriction_plans = Site::PLAN_FEATURES.select {|p,v| v[:no_file_restrictions] == true} no_file_restriction_plans = Site::PLAN_FEATURES.select {|p,v| v[:no_file_restrictions] == true}
no_file_restriction_plans.each do |plan_type,hash| no_file_restriction_plans.each do |plan_type,hash|
@site = Fabricate :site, plan_type: plan_type @site = Fabricate :site, plan_type: plan_type
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav') upload 'flowercrime.wav' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav')
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(File.exists?(@site.files_path('flowercrime.wav'))).must_equal true _(File.exists?(@site.files_path('flowercrime.wav'))).must_equal true
end end
end end
it 'overwrites existing file with new file' do it 'overwrites existing file with new file' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
digest = @site.reload.site_files.first.sha1_hash digest = @site.reload.site_files.first.sha1_hash
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/img/test.jpg', 'image/jpeg') upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/img/test.jpg', 'image/jpeg')
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(@site.reload.changed_count).must_equal 2 _(@site.reload.changed_count).must_equal 2
_(@site.site_files.select {|f| f.path == 'test.jpg'}.length).must_equal 1 _(@site.site_files.select {|f| f.path == 'test.jpg'}.length).must_equal 1
@ -476,10 +437,7 @@ describe 'site_files' do
end end
it 'works with directory path' do it 'works with directory path' do
upload( upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'derpie/derptest',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
_(last_response.body).must_match /successfully uploaded/i _(last_response.body).must_match /successfully uploaded/i
_(File.exists?(@site.files_path('derpie/derptest/test.jpg'))).must_equal true _(File.exists?(@site.files_path('derpie/derptest/test.jpg'))).must_equal true
@ -504,41 +462,29 @@ describe 'site_files' do
end end
it 'works with unicode chars on filename and dir' do it 'works with unicode chars on filename and dir' do
upload( upload '詩經/詩經.jpg' => Rack::Test::UploadedFile.new('./tests/files/詩經.jpg', 'image/jpeg')
'dir' => '詩經',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/詩經.jpg', 'image/jpeg')
)
_(@site.site_files_dataset.where(path: '詩經/詩經.jpg').count).must_equal 1 _(@site.site_files_dataset.where(path: '詩經/詩經.jpg').count).must_equal 1
end end
it 'does not register site changing until root index.html is changed' do it 'does not register site changing until root index.html is changed' do
upload( upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'derpie/derptest',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
_(@site.reload.site_changed).must_equal false _(@site.reload.site_changed).must_equal false
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html') upload 'index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
_(@site.reload.site_changed).must_equal true _(@site.reload.site_changed).must_equal true
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/chunkfive.otf', 'application/vnd.ms-opentype') upload 'chunkfive.otf' => Rack::Test::UploadedFile.new('./tests/files/chunkfive.otf', 'application/vnd.ms-opentype')
_(@site.reload.site_changed).must_equal true _(@site.reload.site_changed).must_equal true
end end
it 'does not store new file if hash matches' do it 'does not store new file if hash matches' do
upload( upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'derpie/derptest',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
_(@site.reload.changed_count).must_equal 1 _(@site.reload.changed_count).must_equal 1
upload( upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
'dir' => 'derpie/derptest',
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
)
_(@site.reload.changed_count).must_equal 1 _(@site.reload.changed_count).must_equal 1
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html') upload 'index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
_(@site.reload.changed_count).must_equal 2 _(@site.reload.changed_count).must_equal 2
end end
@ -554,21 +500,6 @@ describe 'site_files' do
puts "TODO FINISH CLASSIFIER" puts "TODO FINISH CLASSIFIER"
#$trainer.instance_variable_get('@db').redis.flushall #$trainer.instance_variable_get('@db').redis.flushall
end end
=begin
it 'trains files' do
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/classifier/ham.html', 'text/html')
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/classifier/spam.html', 'text/html')
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/classifier/phishing.html', 'text/html')
@site.train 'ham.html'
@site.train 'spam.html', 'spam'
@site.train 'phishing.html', 'phishing'
_(@site.classify('ham.html')).must_equal 'ham'
_(@site.classify('spam.html')).must_equal 'spam'
_(@site.classify('phishing.html')).must_equal 'phishing'
end
=end
end end
end end
end end

View file

@ -1,419 +0,0 @@
<style>
.dz-default {
display: none;
}
.dz-preview {
display: none;
}
.dz-processing {
display: none;
}
.dz-error {
display: none;
}
.dz-image-preview {
display: none;
}
</style>
<div class="header-Outro with-site-image dashboard">
<div class="row content wide">
<div class="col col-50 signup-Area">
<div class="signup-Form">
<fieldset class="content">
<a href="<%= current_site.uri %>" class="screenshot dashboard" style="background-image:url(<%= current_site.screenshot_url('index.html', '540x405') %>);"></a>
</fieldset>
</div>
</div>
<div class="col col-50">
<h2 class="eps"><%= current_site.title %></h2>
<p class="site-url">
<a href="<%= current_site.uri %>" target="_blank"><%= current_site.host %></a>
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site} %>'><i class="fa fa-share-alt"></i> <span>Share</span></a>
</p>
<ul>
<% if current_site.site_updated_at %>
<li>Last updated <%= current_site.site_updated_at.ago.downcase %></li>
<% end %>
<li>Using <strong><%= current_site.space_percentage_used %>% (<%= current_site.total_space_used.to_space_pretty %>) of your <%= current_site.maximum_space.to_space_pretty %></strong>.
<br>
<% unless current_site.is_education || current_site.supporter? %>Need more space? <a href="/supporter">Become a Supporter!</a><% end %></li>
<li><strong><%= current_site.views.format_large_number %></strong> views</li>
</ul>
</div>
</div> <!-- end .row -->
</div> <!-- end .header-Outro -->
<main class="content-Base" role="main">
<div class="content wide">
<% unless current_site.changed_count >= 1 %>
<div class="welcome">
<!-- <div class="close-button"></div> -->
<h4>Hello! Welcome to your new site.</h4>
To get started, click on the <strong>index.html</strong> file below to edit your home page. Once you make changes your website will begin appearing in our <a href="/browse">website gallery</a>. You can add more files (such as images) by dragging them from your computer into the box below. Need help building web sites? Try our <a href="/tutorial/html/">HTML tutorial</a>!
</div>
<% end %>
<%== flash_display %>
<div id="filesDisplay" class="files">
<script>
if(localStorage && localStorage.getItem('viewType') == 'list')
$('#filesDisplay').addClass('list-view')
</script>
<div id="uploadingOverlay" class="uploading-overlay" style="display: none">
<div class="uploading">
<p>Uploading, please wait...</p>
<div id="progressBar" class="progress-bar" style="display: none"><div id="uploadingProgress" class="progress" style="width: 0%"></div></div>
</div>
</div>
<div id="movingOverlay" class="uploading-overlay" style="display: none">
<div class="uploading">
<p>Moving file, please wait...</p>
</div>
</div>
<div class="header">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-default iconview-button" title="Icon View" onclick="iconView()"><i class="fa fa-th"></i></button>
<button type="button" class="btn btn-default listview-button" title="List View" onclick="listView()"><i class="fa fa-list"></i></button>
</div>
<div class="breadcrumbs">
<% if params[:dir].nil? || params[:dir].empty? || params[:dir] == '/' %>
Home
<% else %>
<a href="/dashboard">Home</a>
<% end %>
<% if @dir %>
<% dir_array = @dir.split '/' %>
<% dir_array.each_with_index do |dir,i| %>
<% if i+1 < dir_array.length %>
<a href="/dashboard?dir=<%= Rack::Utils.escape dir_array[1..i].join('/') %>"><%= dir %></a> <i class="fa fa-angle-right"></i>
<% else %>
<%= dir %>
<% end %>
<% end %>
<% end %>
</div>
<div class="actions">
<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="#" class="btn-Action" onclick="clickUploadFiles(); return false"><i class="fa fa-arrow-circle-up"></i> Upload</a>
</div>
</div>
<div class="list">
<form action="/site_files/upload" class="dropzone" id="uploads">
<div class="dz-message" style="display: none"></div>
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
<input name="dir" type="hidden" value="<%= @dir %>">
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
<% @file_list.each do |file| %>
<div class="file filehover">
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
<div class="html-thumbnail html fileimagehover">
<img src="<%= current_site.screenshot_url(file[:path], '210x158') %>" alt="">
<div class="overlay"></div>
</div>
<% elsif file[:is_image] && current_site.thumbnail_exists?(file[:path], '210x158') %>
<div class="html-thumbnail image fileimagehover">
<img src="<%= current_site.thumbnail_url(file[:path], '210x158') %>" alt="">
<div class="overlay"></div>
</div>
<% elsif file[:is_directory] %>
<div class="html-thumbnail folder fileimagehover" ondrop="moveFileToFolder(event)">
<div class="folder-icon"></div>
<div class="overlay"></div>
</div>
<% else %>
<div class="html-thumbnail misc fileimagehover">
<div class="misc-icon"><%= file[:ext][0..3] %></div>
<div class="overlay"></div>
</div>
<% end %>
<a class="title">
<%= file[:name] %>
</a>
<div class="column size">
<% if file[:size] %>
<%= file[:size].to_bytes_pretty %>
<% end %>
</div>
<div class="column updated">
<% if file[:updated_at] %>
<%= file[:updated_at].ago %>
<% end %>
</div>
<div class="overlay">
<div id="<%= Digest::SHA256.hexdigest file[:path] %>" style="display: none"><%= file[:path] %></div>
<% if file[:is_editable] %>
<a href="/site_files/text_editor/<%= file[:path] %>"><i class="fa fa-edit" title="Edit"></i> Edit</a>
<% end %>
<% if file[:is_directory] %>
<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>
<% end %>
<% if file[:is_directory] %>
<a class="link-overlay" href="?dir=<%= Rack::Utils.escape file[:path] %>" title="View <%= file[:path] %>"></a>
<% else %>
<a class="link-overlay" href="<%= current_site.file_uri file[:path] %>" title="View <%= file[:path] == '/index.html' ? 'your site index' : file[:path] %>" target="_blank"></a>
<% end %>
</div>
</div>
<% end %>
</div>
</form>
</div>
</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>
<div class="modal hide fade" id="deleteConfirmModal" tabindex="-1" role="dialog" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<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>
</div>
<div class="modal-footer">
<button class="btn cancel" data-dismiss="modal" aria-hidden="true" type="button">Cancel</button>
<button class="btn-Action btn-danger" type="button" onclick="fileDelete()"><i class="fa fa-trash" title="Delete"></i>Delete</button>
</div>
</div>
<div class="modal hide fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel" aria-hidden="true">
<form method="post" action="/site_files/rename">
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
<input type="hidden" value="<%= @dir %>" name="dir">
<input type="hidden" id="renamePathInput" name="path">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h3 id="renameModalLabel">Rename / Move</h3>
</div>
<div class="modal-body">
<input id="renameNewPathInput" name="new_path" type="text">
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
</div>
<div class="modal-footer">
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn-Action">Rename</button>
</div>
</form>
</div>
<div class="site-actions" style="margin-bottom:25px">
<% if !current_site.plan_feature(:no_file_restrictions) %>
<a href="/site_files/allowed_types">Allowed file types</a> |
<% end %>
<a href="/site_files/download">Download entire site</a> |
<% unless is_education? %>
<a href="/site_files/mount_info">Mount your site as a drive on your computer</a>
<% end %>
</div>
</div>
</main>
<form id="uploadFilesButtonForm" method="POST" action="/site_files/upload" enctype="multipart/form-data" style="display: none" onsubmit="showUploadProgress()">
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
<input name="from_button" type="hidden" value="true">
<input name="dir" type="hidden" value="<%= @dir %>">
<input id="uploadFiles" type="file" name="files[]" multiple onchange="$('#uploadFilesButtonForm').submit()">
</form>
<div class="modal hide fade" id="createDir" tabindex="-1" role="dialog" aria-labelledby="createDirLabel" aria-hidden="true">
<form method="post" action="/site/create_directory">
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
<input type="hidden" value="<%= @dir %>" name="dir">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h3 id="createDirLabel">Create Folder</h3>
</div>
<div class="modal-body">
<input id="newDirInput" name="name" type="text" placeholder="folder_name">
</div>
<div class="modal-footer">
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn-Action">Create</button>
</div>
</form>
</div>
<div class="modal hide fade" id="createFile" tabindex="-1" role="dialog" aria-labelledby="createFileLabel" aria-hidden="true">
<form method="post" action="/site_files/create">
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
<input type="hidden" value="<%= @dir %>" name="dir">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h3 id="createFileLabel">Create New File</h3>
</div>
<div class="modal-body">
<input id="newFileInput" name="filename" type="text" placeholder="newfile.html">
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
</div>
<div class="modal-footer">
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn-Action">Create</button>
</div>
</form>
</div>
<script src="/js/dropzone.min.js"></script>
<script>
var uploadForm = $('#uploadFilesButtonForm')[0];
var deleteForm = $('#deleteFilenameForm')[0];
function moveFileToFolder(event) {
var link = event.dataTransfer.getData("Text");
if(link) link = link.trim();
if(!link || link.startsWith('https://neocities.org/dashboard')) return;
event.preventDefault();
var name = link.split('.neocities.org/').slice(1).join('.neocities.org/');
var oReq = new XMLHttpRequest();
oReq.open("GET", "/site_files/download/" + name, true);
oReq.responseType = "arraybuffer";
$('#movingOverlay').css('display', 'block')
oReq.onload = function() {
var newFile = new File([oReq.response], name);
var dataTransfer = new DataTransfer();
var currentFolder = new URL(location.href).searchParams.get('dir');
if(!currentFolder) currentFolder = '';
else currentFolder = currentFolder + '/';
dataTransfer.items.add(newFile);
$('#uploadFilesButtonForm > input[name="dir"]')[0].value = currentFolder + event.target.parentElement.parentElement.getElementsByClassName('title')[0].innerText.trim();
$('#uploadFiles')[0].files = dataTransfer.files;
$.ajax({
type: uploadForm.method,
url: uploadForm.action,
data: new FormData(uploadForm),
processData: false,
contentType: false,
success: function() {
let csrf = $('#uploadFilesButtonForm > input[name="csrf_token"]')[0].value;
var dReq = new XMLHttpRequest();
dReq.open(deleteForm.method, deleteForm.action, true);
dReq.onload = function() {
location.reload()
}
dReq.setRequestHeader("content-type", 'application/x-www-form-urlencoded');
dReq.send("csrf_token=" + encodeURIComponent(csrf) + "&filename=" + name.replace(/\s/g, '+'));
},
error: function() {
location.reload()
}
});
};
oReq.send();
}
function confirmFileRename(path) {
console.log(path)
$('#renamePathInput').val(path);
$('#renameNewPathInput').val(path);
$('#renameModal').modal();
}
function confirmFileDelete(name) {
$('#deleteFileName').text(name);
$('#deleteConfirmModal').modal();
}
function fileDelete() {
$('#deleteFilenameInput').val($('#deleteFileName').html());
$('#deleteFilenameForm').submit();
}
function clickUploadFiles() {
$("input[id='uploadFiles']").click()
}
function showUploadProgress() {
$('#uploadingOverlay').css('display', 'block')
}
function hideUploadProgress() {
$('#progressBar').css('display', 'none')
$('#uploadingOverlay').css('display', 'none')
}
allUploadsComplete = false
Dropzone.options.uploads = {
paramName: 'files',
maxFilesize: <%= current_site.remaining_space.to_mb %>,
clickable: false,
addRemoveLinks: false,
dictDefaultMessage: '',
uploadMultiple: true,
init: function() {
this.on("completemultiple", function(file) {
if(allUploadsComplete == true)
location.reload()
})
this.on("error", function(file, errorMessage) {
hideUploadProgress()
// Guess a directory upload error
if(file.status == 'error' && file.name.match(/.+\..+/) == null && errorMessage == 'Server responded with 0 code.') {
alert('Recursive directory upload is only supported by the Chrome web browser.')
} else {
location.href = '/dashboard<%= @dir ? "?dir=#{Rack::Utils.escape @dir}" : "" %>'
}
})
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
if(progress == 100)
allUploadsComplete = true
showUploadProgress()
$('#progressBar').css('display', 'block')
$('#uploadingProgress').css('width', progress+'%')
})
this.on("sending", function(file) {
if(file.fullPath !== undefined)
$('#uploads').append('<input type="hidden" name="file_paths[]" value="'+file.fullPath+'">')
})
}
}
$('#createDir').on('shown', function () {
$('#newDirInput').focus();
})
$('#createFile').on('shown', function () {
$('#newFileInput').focus();
})
function listView() {
if(localStorage)
localStorage.setItem('viewType', 'list')
$('#filesDisplay').addClass('list-view')
}
function iconView() {
if(localStorage)
localStorage.removeItem('viewType')
$('#filesDisplay').removeClass('list-view')
}
</script>

108
views/dashboard/files.erb Normal file
View file

@ -0,0 +1,108 @@
<div id="uploadingOverlay" class="uploading-overlay" style="display: none">
<div class="uploading">
<p>Uploading, please wait...</p>
<p id="uploadFileName"></p>
<!-- <div id="progressBar" class="progress-bar" style="display: none"><div id="uploadingProgress" class="progress" style="width: 0%"></div></div> -->
</div>
</div>
<div id="movingOverlay" class="uploading-overlay" style="display: none">
<div class="uploading">
<p>Moving file, please wait...</p>
</div>
</div>
<div class="header">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-default iconview-button" title="Icon View" onclick="iconView()"><i class="fa fa-th"></i></button>
<button type="button" class="btn btn-default listview-button" title="List View" onclick="listView()"><i class="fa fa-list"></i></button>
</div>
<div class="breadcrumbs">
<% if params[:dir].nil? || params[:dir].empty? || params[:dir] == '/' %>
Home
<% else %>
<a href="/dashboard">Home</a>
<% end %>
<% if @dir %>
<% dir_array = @dir.split '/' %>
<% dir_array.each_with_index do |dir,i| %>
<% if i+1 < dir_array.length %>
<a href="/dashboard?dir=<%= Rack::Utils.escape dir_array[1..i].join('/') %>"><%= dir %></a> <i class="fa fa-angle-right"></i>
<% else %>
<%= dir %>
<% end %>
<% end %>
<% end %>
</div>
<div class="actions">
<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>
<div class="list">
<form action="/site_files/upload" class="dropzone" id="uploads">
<div class="dz-message" style="display: none"></div>
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
<input name="dir" type="hidden" value="<%= @dir %>" id="dir">
<div class="upload-Boundary with-instruction">
<% @file_list.each do |file| %>
<div class="file filehover">
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
<div class="html-thumbnail html fileimagehover">
<img src="<%= current_site.screenshot_url(file[:path], '210x158') %>" alt="">
<div class="overlay"></div>
</div>
<% elsif file[:is_image] && current_site.thumbnail_exists?(file[:path], '210x158') %>
<div class="html-thumbnail image fileimagehover">
<img src="<%= current_site.thumbnail_url(file[:path], '210x158') %>" alt="">
<div class="overlay"></div>
</div>
<% elsif file[:is_directory] %>
<div class="html-thumbnail folder fileimagehover" ondrop="moveFileToFolder(event)">
<div class="folder-icon"></div>
<div class="overlay"></div>
</div>
<% else %>
<div class="html-thumbnail misc fileimagehover">
<div class="misc-icon"><%= file[:ext][0..3] %></div>
<div class="overlay"></div>
</div>
<% end %>
<a class="title">
<%= file[:name] %>
</a>
<div class="column size">
<% if file[:size] %>
<%= file[:size].to_bytes_pretty %>
<% end %>
</div>
<div class="column updated">
<% if file[:updated_at] %>
<%= file[:updated_at].ago %>
<% end %>
</div>
<div class="overlay">
<div id="<%= Digest::SHA256.hexdigest file[:path] %>" style="display: none"><%= file[:path] %></div>
<% if file[:is_editable] %>
<a href="/site_files/text_editor?filename=<%= Rack::Utils.escape file[:path] %>"><i class="fa fa-edit" title="Edit"></i> Edit</a>
<% end %>
<% if file[:is_directory] %>
<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>
<% end %>
<% if file[:is_directory] %>
<a class="link-overlay" href="?dir=<%= Rack::Utils.escape file[:path] %>" title="View <%= file[:path] %>"></a>
<% else %>
<a class="link-overlay" href="<%= current_site.file_uri file[:path] %>" title="View <%= file[:path] == '/index.html' ? 'your site index' : file[:path] %>" target="_blank"></a>
<% end %>
</div>
</div>
<% end %>
</div>
</form>
</div>

173
views/dashboard/index.erb Normal file
View file

@ -0,0 +1,173 @@
<script src="/js/dropzone-min.js"></script>
<style>
.dz-default {
display: none;
}
.dz-preview {
display: none;
}
.dz-processing {
display: none;
}
.dz-error {
display: none;
}
.dz-image-preview {
display: none;
}
</style>
<div class="header-Outro with-site-image dashboard">
<div class="row content wide">
<div class="col col-50 signup-Area">
<div class="signup-Form">
<fieldset class="content">
<a href="<%= current_site.uri %>" class="screenshot dashboard" style="background-image:url(<%= current_site.screenshot_url('index.html', '540x405') %>);"></a>
</fieldset>
</div>
</div>
<div class="col col-50">
<h2 class="eps"><%= current_site.title %></h2>
<p class="site-url">
<a href="<%= current_site.uri %>" target="_blank"><%= current_site.host %></a>
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site} %>'><i class="fa fa-share-alt"></i> <span>Share</span></a>
</p>
<ul>
<% if current_site.site_updated_at %>
<li>Last updated <%= current_site.site_updated_at.ago.downcase %></li>
<% end %>
<li>Using <strong><%= current_site.space_percentage_used %>% (<%= current_site.total_space_used.to_space_pretty %>) of your <%= current_site.maximum_space.to_space_pretty %></strong>.
<br>
<% unless current_site.is_education || current_site.supporter? %>Need more space? <a href="/supporter">Become a Supporter!</a><% end %></li>
<li><strong><%= current_site.views.format_large_number %></strong> views</li>
</ul>
</div>
</div> <!-- end .row -->
</div> <!-- end .header-Outro -->
<main class="content-Base" role="main">
<div class="content wide">
<% unless current_site.changed_count >= 1 %>
<div class="welcome">
<!-- <div class="close-button"></div> -->
<h4>Hello! Welcome to your new site.</h4>
To get started, click on the <strong>index.html</strong> file below to edit your home page. Once you make changes your website will begin appearing in our <a href="/browse">website gallery</a>. You can add more files (such as images) by dragging them from your computer into the box below. Need help building web sites? Try our <a href="/tutorial/html/">HTML tutorial</a>!
</div>
<% end %>
<% if flash.keys.length > 0 %>
<div id="alertDialogue" class="alert alert-block alert-<%= flash.keys.first %>" style="display: block; max-height: 200px; overflow-y: auto;">
<% flash.keys.select {|k| [:success, :error, :errors].include?(k)}.each do |key| %>
<%== flash[key] %>
<% end %>
</div>
<% else %>
<div id="alertDialogue" class="alert alert-block alert-<%= flash.keys.first || 'success' %>" style="display: none; max-height: 200px; overflow-y: auto;"></div>
<% end %>
<div id="filesDisplay" class="files">
<%== 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>
<div class="modal hide fade" id="deleteConfirmModal" tabindex="-1" role="dialog" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<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>
</div>
<div class="modal-footer">
<button class="btn cancel" data-dismiss="modal" aria-hidden="true" type="button">Cancel</button>
<button class="btn-Action btn-danger" type="button" onclick="fileDelete()"><i class="fa fa-trash" title="Delete"></i>Delete</button>
</div>
</div>
<div class="modal hide fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel" aria-hidden="true">
<form method="post" action="/site_files/rename">
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
<input type="hidden" value="<%= @dir %>" name="dir">
<input type="hidden" id="renamePathInput" name="path">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h3 id="renameModalLabel">Rename / Move</h3>
</div>
<div class="modal-body">
<input id="renameNewPathInput" name="new_path" type="text" style="width: 100%">
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
</div>
<div class="modal-footer">
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn-Action">Rename</button>
</div>
</form>
</div>
<div class="site-actions" style="margin-bottom:25px">
<% if !current_site.plan_feature(:no_file_restrictions) %>
<a href="/site_files/allowed_types">Allowed file types</a> |
<% end %>
<a href="/site_files/download">Download entire site</a> |
<% unless is_education? %>
<a href="/site_files/mount_info">Mount your site as a drive on your computer</a>
<% end %>
</div>
</div>
</main>
<form id="uploadFilesButtonForm" method="POST" action="/api/upload" enctype="multipart/form-data" style="display: none" onsubmit="event.preventDefault(); showUploadProgress(); uploadFileFromButton();">
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
<input name="from_button" type="hidden" value="true">
<input name="dir" type="hidden" id="dir" value="<%= @dir %>">
<input id="uploadFiles" type="file" name="file" multiple>
</form>
<div class="modal hide fade" id="createDir" tabindex="-1" role="dialog" aria-labelledby="createDirLabel" aria-hidden="true">
<form method="post" action="/site/create_directory">
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
<input type="hidden" value="<%= @dir %>" name="dir">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h3 id="createDirLabel">Create Folder</h3>
</div>
<div class="modal-body">
<input id="newDirInput" name="name" type="text" placeholder="folder_name">
</div>
<div class="modal-footer">
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn-Action">Create</button>
</div>
</form>
</div>
<div class="modal hide fade" id="createFile" tabindex="-1" role="dialog" aria-labelledby="createFileLabel" aria-hidden="true">
<form method="post" action="/site_files/create">
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
<input type="hidden" value="<%= @dir %>" name="dir">
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h3 id="createFileLabel">Create New File</h3>
</div>
<div class="modal-body">
<input id="newFileInput" name="filename" type="text" placeholder="newfile.html">
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
</div>
<div class="modal-footer">
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn-Action">Create</button>
</div>
</form>
</div>
<script src="/js/dashboard.js"><script>

View file

@ -17,10 +17,6 @@
<meta http-equiv="Expires" content="0"> <meta http-equiv="Expires" content="0">
<% end %> <% end %>
<!--[if lt IE 9]>
<script type="text/javascript" src="/js/html5.min.js"></script>
<![endif]-->
<script src="/js/jquery-1.11.0.min.js"></script> <script src="/js/jquery-1.11.0.min.js"></script>
<script src="/js/highlight.pack.js"></script> <script src="/js/highlight.pack.js"></script>

View file

@ -132,17 +132,31 @@
function saveTextFile(quit) { function saveTextFile(quit) {
if(unsavedChanges == false) if(unsavedChanges == false)
return return
var formData = new FormData();
var fileContent = new Blob([editor.getValue()], { type: 'text/html' });
formData.append('<%= escape_javascript @filename %>', fileContent, '<%= escape_javascript @filename %>');
formData.append('csrf_token', '<%= escape_javascript csrf_token %>');
formData.append('username', '<%= escape_javascript current_site.username %>');
$.ajax({ $.ajax({
url: "/site_files/upload?csrf_token=<%= Rack::Utils.escape csrf_token %>&filename=<%= Rack::Utils.escape @filename %>&site_id=<%= current_site.id %>", url: '/api/upload',
data: editor.getValue(), data: formData,
processData: false, processData: false,
contentType: false, contentType: false,
type: 'POST', type: 'POST',
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
var errorMessage = 'There has been an error saving your file, please try again. If it continues to fail, make a copy of the file locally so you don\'t lose your changes!' var errorMessage = 'There has been an error saving your file, please try again. If it continues to fail, make a copy of the file locally so you don\'t lose your changes!'
if(jqXHR.responseText) if(jqXHR.responseText) {
errorMessage += ' ERROR MESSAGE: '+jqXHR.responseText try {
// Attempt to parse the JSON responseText to get the error message
var parsedResponse = JSON.parse(jqXHR.responseText);
errorMessage += ' ERROR MESSAGE: ' + parsedResponse.message;
} catch (error) {
}
}
$('#saveButton').tooltip('show') $('#saveButton').tooltip('show')
$('#editorUpdates span').text(errorMessage) $('#editorUpdates span').text(errorMessage)