mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +02:00
Merge pull request #497 from neocities/fileuploadrefactor
File Upload Refactor
This commit is contained in:
commit
d506f6cb3e
21 changed files with 704 additions and 647 deletions
59
app/api.rb
59
app/api.rb
|
@ -42,23 +42,56 @@ get '/api/list' do
|
|||
end
|
||||
|
||||
def extract_files(params, files = [])
|
||||
# Check if the entire input is directly an array of files
|
||||
if params.is_a?(Array)
|
||||
params.each do |item|
|
||||
# Call extract_files on each item if it's an Array or Hash to handle nested structures
|
||||
if item.is_a?(Array) || item.is_a?(Hash)
|
||||
extract_files(item, 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?(Hash) || value.is_a?(Array)
|
||||
# If the value is a Hash or Array, recursively search for more files.
|
||||
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
|
||||
files
|
||||
end
|
||||
|
||||
post '/api/upload' do
|
||||
require_api_credentials
|
||||
|
||||
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?
|
||||
|
||||
uploaded_size = files.collect {|f| f[:tempfile].size}.inject{|sum,x| sum + x }
|
||||
|
@ -68,16 +101,28 @@ post '/api/upload' do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
files.each do |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
|
||||
|
||||
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
|
||||
|
||||
|
@ -191,7 +236,7 @@ post '/api/:name' do
|
|||
end
|
||||
|
||||
def require_api_credentials
|
||||
return true if current_site
|
||||
return true if current_site && csrf_safe?
|
||||
|
||||
if !request.env['HTTP_AUTHORIZATION'].nil?
|
||||
init_api_credentials
|
||||
|
|
|
@ -8,7 +8,7 @@ get '/dashboard' do
|
|||
current_site.save_changes validate: false
|
||||
end
|
||||
|
||||
erb :'dashboard'
|
||||
erb :'dashboard/index'
|
||||
end
|
||||
|
||||
def dashboard_init
|
||||
|
@ -30,3 +30,11 @@ def dashboard_init
|
|||
@dir = params[:dir]
|
||||
@file_list = current_site.file_list @dir
|
||||
end
|
||||
|
||||
get '/dashboard/files' do
|
||||
require_login
|
||||
dashboard_init
|
||||
dont_browser_cache
|
||||
|
||||
erb :'dashboard/files', layout: false
|
||||
end
|
|
@ -75,7 +75,9 @@ post '/site_files/create' do
|
|||
end
|
||||
|
||||
def file_upload_response(error=nil)
|
||||
flash[:error] = error if error
|
||||
if error
|
||||
flash[:error] = error
|
||||
end
|
||||
|
||||
if params[:from_button]
|
||||
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?
|
||||
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
|
||||
require_login
|
||||
path = HTMLEntities.new.decode params[:filename]
|
||||
|
@ -246,7 +177,16 @@ get %r{\/site_files\/text_editor\/(.+)} do
|
|||
dont_browser_cache
|
||||
|
||||
@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
|
||||
|
||||
@ace_mode = case extname
|
||||
when /htm|html/ then 'html'
|
||||
when /js/ then 'javascript'
|
||||
|
|
|
@ -131,3 +131,15 @@ def hcaptcha_valid?
|
|||
false
|
||||
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
|
|
@ -84,7 +84,7 @@ class Site < Sequel::Model
|
|||
SCREENSHOT_RESOLUTIONS = ['540x405', '210x158', '100x100', '50x50']
|
||||
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 = [
|
||||
/^VBS/,
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
58
public/img/drag-drop.svg
Normal file
58
public/img/drag-drop.svg
Normal 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
157
public/js/dashboard.js
Normal 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
2
public/js/dropzone-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dropzone-min.js.map
Normal file
1
public/js/dropzone-min.js.map
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dropzone.min.js
vendored
1
public/js/dropzone.min.js
vendored
File diff suppressed because one or more lines are too long
8
public/js/html5.min.js
vendored
8
public/js/html5.min.js
vendored
|
@ -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);
|
|
@ -60,7 +60,7 @@ a{
|
|||
@import 'project-Footer'; // Project Specific Footer Styling
|
||||
|
||||
.alert-error {
|
||||
background-color:#F5BA00; color:#fff;
|
||||
background-color:#f39c12; color:#fff;
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
|
|
@ -352,7 +352,7 @@
|
|||
min-height: 300px;
|
||||
}
|
||||
.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) {
|
||||
background: 0;
|
||||
|
@ -370,7 +370,7 @@
|
|||
font-style: italic;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 400px;
|
||||
width: 90%;
|
||||
margin-top: 14%;
|
||||
padding: 25px 40px 28px 40px;
|
||||
-webkit-box-shadow: 1px 1px 21px 5px rgba(50, 50, 50, 0.5);
|
||||
|
|
|
@ -308,6 +308,18 @@ describe 'api' do
|
|||
_(site_file_exists?('test.jpg')).must_equal true
|
||||
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
|
||||
create_site
|
||||
@site.generate_api_key!
|
||||
|
@ -329,6 +341,34 @@ describe 'api' do
|
|||
_(site_file_exists?('test.jpg')).must_equal true
|
||||
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
|
||||
create_site
|
||||
@site.generate_api_key!
|
||||
|
|
|
@ -9,7 +9,7 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def delete_file(hash)
|
||||
|
@ -45,7 +45,7 @@ describe 'site_files' do
|
|||
|
||||
it 'works with html file' do
|
||||
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/notindex.html', 'text/html')
|
||||
upload 'files[]' => uploaded_file
|
||||
upload 'notindex.html' => uploaded_file
|
||||
PurgeCacheWorker.jobs.clear
|
||||
testfile = @site.site_files_dataset.where(path: 'notindex.html').first
|
||||
testfile.rename 'notindex2.html'
|
||||
|
@ -55,7 +55,7 @@ describe 'site_files' do
|
|||
|
||||
it 'renames in same path' do
|
||||
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).wont_equal nil
|
||||
|
@ -66,9 +66,6 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
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' }}
|
||||
_(last_response.headers['Location']).must_match /dashboard/
|
||||
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
|
||||
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
|
||||
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.each do |plan_type,hash|
|
||||
@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
|
||||
res = testfile.rename('flowercrime.exe')
|
||||
_(res.first).must_equal true
|
||||
|
@ -126,18 +123,13 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'changes path of files and dirs within directory when changed' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'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')
|
||||
)
|
||||
upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'test/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
|
||||
|
||||
PurgeCacheWorker.jobs.clear
|
||||
|
||||
@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\/test.jpg/}.length).must_equal 1
|
||||
_(@site.site_files.select {|sf| sf.path =~ /test\/test.jpg/}.length).must_equal 0
|
||||
|
@ -146,14 +138,8 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'doesnt wipe out existing file' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'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')
|
||||
)
|
||||
upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'test/index.html' => 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).must_equal [false, 'file already exists']
|
||||
|
@ -172,15 +158,14 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'works with unicode characters' do
|
||||
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')
|
||||
@site.site_files_dataset.where(path: 'test.jpg').first.rename("HELL💩؋.jpg")
|
||||
_(@site.site_files_dataset.where(path: "HELL💩؋.jpg").first).wont_equal nil
|
||||
end
|
||||
|
||||
it 'scrubs weird carriage return shit characters' do
|
||||
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 {
|
||||
@site.site_files_dataset.where(path: 'test.jpg').first.rename("\r\n\t.jpg")
|
||||
}).must_raise ArgumentError
|
||||
|
@ -196,7 +181,7 @@ describe 'site_files' do
|
|||
it 'works' do
|
||||
initial_space_used = @site.space_used
|
||||
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'files[]' => uploaded_file
|
||||
upload 'test.jpg' => uploaded_file
|
||||
|
||||
PurgeCacheWorker.jobs.clear
|
||||
|
||||
|
@ -217,30 +202,21 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
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)'
|
||||
_(@site.reload.site_files.select {|f| f.path =~ /#{Regexp.quote '8)'}/}.length).must_equal 0
|
||||
end
|
||||
|
||||
it 'deletes with escaped apostrophe' do
|
||||
upload(
|
||||
'dir' => "test'ing",
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
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"
|
||||
_(@site.reload.site_files.select {|s| s.path == "test'ing"}.length).must_equal 0
|
||||
end
|
||||
|
||||
it 'deletes a directory and all files in it' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload(
|
||||
'dir' => '',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload 'test/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
|
||||
space_used = @site.reload.space_used
|
||||
delete_file filename: 'test'
|
||||
|
@ -253,10 +229,7 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'deletes records for nested directories' do
|
||||
upload(
|
||||
'dir' => 'derp/ing/tons',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload 'derp/ing/tons/test.jpg' => 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']
|
||||
|
||||
|
@ -274,16 +247,11 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'goes back to deleting directory' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
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(
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
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
|
||||
|
@ -291,17 +259,17 @@ describe 'site_files' do
|
|||
|
||||
describe 'upload' 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
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -312,7 +280,7 @@ describe 'site_files' do
|
|||
file.write("derp")
|
||||
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
|
||||
ensure
|
||||
FileUtils.rm file_path
|
||||
|
@ -320,20 +288,17 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'fails with path greater than limit' do
|
||||
upload(
|
||||
'dir' => (("a" * 50 + "/") * (SiteFile::FILE_PATH_CHARACTER_LIMIT / 50 - 1) + "a" * 50),
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload "#{(("a" * 50 + "/") * (SiteFile::FILE_PATH_CHARACTER_LIMIT / 50 - 1) + "a" * 50)}/test.jpg" => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
_(last_response.body).must_match /path is too long/i
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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.new.perform @site.username, '/notindex.html'
|
||||
_(PurgeCacheWorker.jobs.first['args'].last).must_equal '/notindex'
|
||||
|
@ -341,7 +306,7 @@ describe 'site_files' do
|
|||
|
||||
it 'succeeds with index.html file' do
|
||||
_(@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
|
||||
_(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
|
||||
initial_space_used = @site.space_used
|
||||
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')
|
||||
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.space_used).must_equal @site.actual_space_used
|
||||
end
|
||||
|
@ -377,27 +342,22 @@ describe 'site_files' do
|
|||
it 'does not change title for subdir index.html' do
|
||||
title = @site.title
|
||||
upload(
|
||||
'dir' => 'derpie',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
|
||||
'derpie/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
|
||||
)
|
||||
_(@site.reload.title).must_equal title
|
||||
end
|
||||
|
||||
it 'purges cache for /subdir/' do # (not /subdir which is just a redirect to /subdir/)
|
||||
upload(
|
||||
'dir' => 'subdir',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
|
||||
'subdir/index.html' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html')
|
||||
)
|
||||
_(PurgeCacheWorker.jobs.select {|j| j['args'].last == '/subdir/'}.length).must_equal 1
|
||||
end
|
||||
|
||||
it 'succeeds with multiple files' do
|
||||
upload(
|
||||
'file_paths' => ['one/test.jpg', 'two/test.jpg'],
|
||||
'files' => [
|
||||
Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
|
||||
Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
]
|
||||
'one/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
|
||||
'two/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
|
||||
_(@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
|
||||
initial_space_used = @site.space_used
|
||||
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
|
||||
_(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
|
||||
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
|
||||
_(File.exists?(@site.files_path('te[s]t.jpg'))).must_equal true
|
||||
end
|
||||
|
||||
it 'sets site changed to false if index is empty' do
|
||||
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
|
||||
_(@site.empty_index?).must_equal true
|
||||
_(@site.site_changed).must_equal false
|
||||
end
|
||||
|
||||
it 'fails with unsupported file' do
|
||||
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav')
|
||||
_(last_response.body).must_match /only supported by.+supporter account/i
|
||||
upload 'flowercrime.wav' => Rack::Test::UploadedFile.new('./tests/files/flowercrime.wav', 'audio/x-wav')
|
||||
|
||||
_(JSON.parse(last_response.body)['error_type']).must_equal 'invalid_file_type'
|
||||
_(File.exists?(@site.files_path('flowercrime.wav'))).must_equal false
|
||||
_(@site.site_changed).must_equal false
|
||||
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.each do |plan_type,hash|
|
||||
@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
|
||||
_(File.exists?(@site.files_path('flowercrime.wav'))).must_equal true
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
_(@site.reload.changed_count).must_equal 2
|
||||
_(@site.site_files.select {|f| f.path == 'test.jpg'}.length).must_equal 1
|
||||
|
@ -476,10 +437,7 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'works with directory path' do
|
||||
upload(
|
||||
'dir' => 'derpie/derptest',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
_(last_response.body).must_match /successfully uploaded/i
|
||||
_(File.exists?(@site.files_path('derpie/derptest/test.jpg'))).must_equal true
|
||||
|
||||
|
@ -504,41 +462,29 @@ describe 'site_files' do
|
|||
end
|
||||
|
||||
it 'works with unicode chars on filename and dir' do
|
||||
upload(
|
||||
'dir' => '詩經',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/詩經.jpg', 'image/jpeg')
|
||||
)
|
||||
upload '詩經/詩經.jpg' => Rack::Test::UploadedFile.new('./tests/files/詩經.jpg', 'image/jpeg')
|
||||
_(@site.site_files_dataset.where(path: '詩經/詩經.jpg').count).must_equal 1
|
||||
end
|
||||
|
||||
it 'does not register site changing until root index.html is changed' do
|
||||
upload(
|
||||
'dir' => 'derpie/derptest',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
_(@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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
it 'does not store new file if hash matches' do
|
||||
upload(
|
||||
'dir' => 'derpie/derptest',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
_(@site.reload.changed_count).must_equal 1
|
||||
|
||||
upload(
|
||||
'dir' => 'derpie/derptest',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload 'derpie/derptest/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
_(@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
|
||||
end
|
||||
|
||||
|
@ -554,21 +500,6 @@ describe 'site_files' do
|
|||
puts "TODO FINISH CLASSIFIER"
|
||||
#$trainer.instance_variable_get('@db').redis.flushall
|
||||
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
|
||||
|
|
|
@ -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
108
views/dashboard/files.erb
Normal 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
173
views/dashboard/index.erb
Normal 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>
|
|
@ -17,10 +17,6 @@
|
|||
<meta http-equiv="Expires" content="0">
|
||||
<% 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/highlight.pack.js"></script>
|
||||
|
|
|
@ -132,17 +132,31 @@
|
|||
function saveTextFile(quit) {
|
||||
if(unsavedChanges == false)
|
||||
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({
|
||||
url: "/site_files/upload?csrf_token=<%= Rack::Utils.escape csrf_token %>&filename=<%= Rack::Utils.escape @filename %>&site_id=<%= current_site.id %>",
|
||||
data: editor.getValue(),
|
||||
url: '/api/upload',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
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!'
|
||||
|
||||
if(jqXHR.responseText)
|
||||
errorMessage += ' ERROR MESSAGE: '+jqXHR.responseText
|
||||
if(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')
|
||||
$('#editorUpdates span').text(errorMessage)
|
||||
|
|
Loading…
Add table
Reference in a new issue