mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +02:00
Folders.
This commit is contained in:
parent
605fdce9be
commit
2f73732daa
17 changed files with 508 additions and 288 deletions
6
Gemfile
6
Gemfile
|
@ -7,7 +7,6 @@ gem 'bcrypt'
|
|||
gem 'sinatra-flash', require: 'sinatra/flash'
|
||||
gem 'sinatra-xsendfile', require: 'sinatra/xsendfile'
|
||||
gem 'puma', require: nil
|
||||
gem 'rubyzip', require: 'zip'
|
||||
gem 'rack-recaptcha', require: 'rack/recaptcha'
|
||||
gem 'rmagick', require: nil
|
||||
gem 'sidekiq'
|
||||
|
@ -19,6 +18,8 @@ gem 'erubis'
|
|||
gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'
|
||||
gem 'screencap'
|
||||
gem 'cocaine'
|
||||
gem 'zipruby'
|
||||
gem 'always_verify_ssl_certificates'
|
||||
|
||||
platform :mri do
|
||||
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
||||
|
@ -54,13 +55,12 @@ group :test do
|
|||
gem 'minitest'
|
||||
gem 'minitest-reporters', require: 'minitest/reporters'
|
||||
gem 'rack-test', require: 'rack/test'
|
||||
gem 'webmock'
|
||||
gem 'mocha', require: nil
|
||||
gem 'rake', require: nil
|
||||
gem 'poltergeist'
|
||||
gem 'phantomjs', require: 'phantomjs/poltergeist'
|
||||
gem 'capybara'
|
||||
gem 'capybara_minitest_spec'
|
||||
gem 'rack_session_access', require: nil
|
||||
|
||||
platform :mri do
|
||||
gem 'simplecov', require: nil
|
||||
|
|
32
Gemfile.lock
32
Gemfile.lock
|
@ -18,6 +18,7 @@ GEM
|
|||
tzinfo (~> 1.1)
|
||||
addressable (2.3.6)
|
||||
ago (0.1.5)
|
||||
always_verify_ssl_certificates (0.3.0)
|
||||
ansi (1.4.3)
|
||||
autoparse (0.3.3)
|
||||
addressable (>= 2.3.1)
|
||||
|
@ -25,7 +26,7 @@ GEM
|
|||
multi_json (>= 1.0.0)
|
||||
bcrypt (3.1.7)
|
||||
builder (3.2.2)
|
||||
capybara (2.2.1)
|
||||
capybara (2.4.1)
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
|
@ -44,8 +45,6 @@ GEM
|
|||
coderay (1.1.0)
|
||||
columnize (0.3.6)
|
||||
connection_pool (2.0.0)
|
||||
crack (0.4.2)
|
||||
safe_yaml (~> 1.0.0)
|
||||
debugger (1.6.6)
|
||||
columnize (>= 0.3.1)
|
||||
debugger-linecache (~> 1.2.0)
|
||||
|
@ -89,7 +88,7 @@ GEM
|
|||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.5.3)
|
||||
mini_portile (0.6.0)
|
||||
minitest (5.3.1)
|
||||
minitest-reporters (1.0.2)
|
||||
ansi
|
||||
|
@ -98,13 +97,13 @@ GEM
|
|||
powerbar
|
||||
mocha (1.0.0)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.9.2)
|
||||
multi_json (1.10.1)
|
||||
multipart-post (2.0.0)
|
||||
nokogiri (1.6.1)
|
||||
mini_portile (~> 0.5.0)
|
||||
nokogiri (1.6.3.1)
|
||||
mini_portile (= 0.6.0)
|
||||
pg (0.17.1)
|
||||
phantomjs (1.9.7.0)
|
||||
poltergeist (1.5.0)
|
||||
poltergeist (1.5.1)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -129,6 +128,9 @@ GEM
|
|||
json
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rack_session_access (0.1.1)
|
||||
builder (>= 2.0.0)
|
||||
rack (>= 1.0.0)
|
||||
rainbows (4.6.1)
|
||||
kgio (~> 2.5)
|
||||
rack (~> 1.1)
|
||||
|
@ -142,8 +144,6 @@ GEM
|
|||
mime-types (>= 1.16)
|
||||
retriable (1.4.1)
|
||||
rmagick (2.13.2)
|
||||
rubyzip (1.1.2)
|
||||
safe_yaml (1.0.1)
|
||||
sass (3.3.8)
|
||||
screencap (0.1.1)
|
||||
phantomjs
|
||||
|
@ -191,20 +191,18 @@ GEM
|
|||
rack
|
||||
raindrops (~> 0.7)
|
||||
uuidtools (2.1.4)
|
||||
webmock (1.17.4)
|
||||
addressable (>= 2.2.7)
|
||||
crack (>= 0.3.2)
|
||||
websocket-driver (0.3.2)
|
||||
websocket-driver (0.3.4)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
zipruby (0.3.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
ago
|
||||
always_verify_ssl_certificates
|
||||
bcrypt
|
||||
capybara
|
||||
capybara_minitest_spec
|
||||
cocaine
|
||||
erubis
|
||||
|
@ -228,12 +226,12 @@ DEPENDENCIES
|
|||
puma
|
||||
rack-recaptcha
|
||||
rack-test
|
||||
rack_session_access
|
||||
rainbows
|
||||
rake
|
||||
redis
|
||||
rmagick
|
||||
ruby-debug
|
||||
rubyzip
|
||||
sass
|
||||
screencap
|
||||
sequel (= 4.8.0)
|
||||
|
@ -246,4 +244,4 @@ DEPENDENCIES
|
|||
sinatra-xsendfile
|
||||
stripe!
|
||||
tilt
|
||||
webmock
|
||||
zipruby
|
||||
|
|
2
Rakefile
2
Rakefile
|
@ -7,7 +7,7 @@ end
|
|||
desc "Run all tests"
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << "spec"
|
||||
t.test_files = FileList['tests/*_tests.rb']
|
||||
t.test_files = FileList['tests/**/*_tests.rb']
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
|
|
82
app.rb
82
app.rb
|
@ -452,6 +452,13 @@ end
|
|||
|
||||
get '/dashboard' do
|
||||
require_login
|
||||
|
||||
if params[:dir] && params[:dir][0] != '/'
|
||||
params[:dir] = '/'+params[:dir]
|
||||
end
|
||||
|
||||
@dir = params[:dir]
|
||||
@file_list = current_site.file_list @dir
|
||||
erb :'dashboard'
|
||||
end
|
||||
|
||||
|
@ -562,7 +569,7 @@ post '/change_name' do
|
|||
end
|
||||
|
||||
old_host = current_site.host
|
||||
old_file_paths = current_site.file_list.collect {|f| f.filename}
|
||||
old_file_paths = current_site.file_list.collect {|f| f[:path]}
|
||||
|
||||
current_site.username = params[:name]
|
||||
|
||||
|
@ -629,7 +636,8 @@ def file_upload_response(error=nil)
|
|||
@error = error
|
||||
halt 200, erb(:'dashboard')
|
||||
else
|
||||
redirect '/dashboard'
|
||||
query_string = params[:dir] ? "?"+Rack::Utils.build_query(dir: params[:dir]) : ''
|
||||
redirect "/dashboard#{query_string}"
|
||||
end
|
||||
else
|
||||
halt http_error_code, error if error
|
||||
|
@ -637,6 +645,20 @@ def file_upload_response(error=nil)
|
|||
end
|
||||
end
|
||||
|
||||
post '/site/create_directory' do
|
||||
require_login
|
||||
|
||||
path = "#{params[:dir] || ''}/#{params[:name]}"
|
||||
|
||||
result = current_site.create_directory path
|
||||
|
||||
if result != true
|
||||
flash[:error] = e.message
|
||||
end
|
||||
|
||||
redirect "/dashboard?dir=#{Rack::Utils.escape params[:dir]}"
|
||||
end
|
||||
|
||||
post '/site_files/upload' do
|
||||
require_login
|
||||
@errors = []
|
||||
|
@ -647,12 +669,12 @@ post '/site_files/upload' do
|
|||
end
|
||||
|
||||
params[:files].each do |file|
|
||||
file[:filename] = "#{params[:dir]}/#{file[:filename]}" if params[:dir]
|
||||
if current_site.file_size_too_large? file[:tempfile].size
|
||||
file_upload_response "#{file[:filename]} is too large, upload cancelled."
|
||||
file_upload_response "#{params[:dir]}/#{file[:filename]} is too large, upload cancelled."
|
||||
end
|
||||
|
||||
if !Site.valid_file_type? file
|
||||
file_upload_response "#{file[:filename]}: file type (or content in file) is not allowed on Neocities, upload cancelled."
|
||||
file_upload_response "#{params[:dir]}/#{file[:filename]}: file type (or content in file) is not allowed on Neocities, upload cancelled."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -664,7 +686,7 @@ post '/site_files/upload' do
|
|||
|
||||
results = []
|
||||
params[:files].each do |file|
|
||||
results << current_site.store_file(Site.sanitize_filename(file[:filename]), file[:tempfile])
|
||||
results << current_site.store_file(file[:filename], file[:tempfile])
|
||||
end
|
||||
|
||||
current_site.increment_changed_count if results.include?(true)
|
||||
|
@ -674,20 +696,18 @@ end
|
|||
|
||||
post '/site_files/delete' do
|
||||
require_login
|
||||
sanitized_filename = Site.sanitize_filename params[:filename]
|
||||
current_site.delete_file params[:filename]
|
||||
|
||||
current_site.delete_file(sanitized_filename)
|
||||
|
||||
flash[:success] = "Deleted file #{params[:filename]}."
|
||||
flash[:success] = "Deleted #{params[:filename]}."
|
||||
redirect '/dashboard'
|
||||
end
|
||||
|
||||
get '/site_files/:username.zip' do |username|
|
||||
require_login
|
||||
zipfile = current_site.files_zip
|
||||
zipfile_path = current_site.files_zip
|
||||
content_type 'application/octet-stream'
|
||||
attachment "#{current_site.username}.zip"
|
||||
zipfile
|
||||
attachment "neocities-#{current_site.username}.zip"
|
||||
send_file zipfile_path
|
||||
end
|
||||
|
||||
get '/site_files/download/:filename' do |filename|
|
||||
|
@ -722,9 +742,7 @@ post '/site_files/save/:filename' do |filename|
|
|||
halt 'File is too large to fit in your space, it has NOT been saved. Please make a local copy and then try to reduce the size.'
|
||||
end
|
||||
|
||||
sanitized_filename = Site.sanitize_filename filename
|
||||
|
||||
current_site.store_file sanitized_filename, tempfile
|
||||
current_site.store_file filename, tempfile
|
||||
|
||||
'ok'
|
||||
end
|
||||
|
@ -937,14 +955,11 @@ end
|
|||
|
||||
post '/api/upload' do
|
||||
require_api_credentials
|
||||
|
||||
files = []
|
||||
|
||||
params.each do |k,v|
|
||||
next unless v.is_a?(Hash) && v[:tempfile]
|
||||
filename = k.to_s
|
||||
api_error(400, 'bad_filename', "#{filename} is not a valid filename, files not uploaded") unless Site.valid_filename? filename
|
||||
files << {filename: filename, tempfile: v[:tempfile]}
|
||||
path = k.to_s
|
||||
files << {filename: k || v[:filename], tempfile: v[:tempfile]}
|
||||
end
|
||||
|
||||
api_error 400, 'missing_files', 'you must provide files to upload' if files.empty?
|
||||
|
@ -959,6 +974,10 @@ post '/api/upload' do
|
|||
if !Site.valid_file_type?(file)
|
||||
api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type (or contains not allowed content), files have not been uploaded"
|
||||
end
|
||||
|
||||
if File.directory? file[:filename]
|
||||
api_error 400, 'directory_exists', 'this name is being used by a directory, cannot continue'
|
||||
end
|
||||
end
|
||||
|
||||
results = []
|
||||
|
@ -976,26 +995,25 @@ post '/api/delete' do
|
|||
|
||||
api_error 400, 'missing_filenames', 'you must provide files to delete' if params[:filenames].nil? || params[:filenames].empty?
|
||||
|
||||
filenames = []
|
||||
|
||||
params[:filenames].each do |filename|
|
||||
unless filename.is_a?(String) && Site.valid_filename?(filename)
|
||||
api_error 400, 'bad_filename', "#{filename} is not a valid filename, canceled deleting"
|
||||
paths = []
|
||||
params[:filenames].each do |path|
|
||||
unless path.is_a?(String) && Site.valid_path?(path)
|
||||
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
||||
end
|
||||
|
||||
if !current_site.file_exists?(filename)
|
||||
api_error 400, 'missing_files', "#{filename} was not found on your site, canceled deleting"
|
||||
if !current_site.file_exists?(path)
|
||||
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
||||
end
|
||||
|
||||
if filename == 'index.html'
|
||||
if path == 'index.html'
|
||||
api_error 400, 'cannot_delete_index', 'you cannot delete your index.html file, canceled deleting'
|
||||
end
|
||||
|
||||
filenames << filename
|
||||
paths << path
|
||||
end
|
||||
|
||||
filenames.each do |filename|
|
||||
current_site.delete_file(filename)
|
||||
paths.each do |path|
|
||||
current_site.delete_file(path)
|
||||
end
|
||||
|
||||
api_success 'file(s) have been deleted'
|
||||
|
|
|
@ -7,7 +7,6 @@ Encoding.default_external = 'UTF-8'
|
|||
require 'yaml'
|
||||
require 'json'
|
||||
require 'logger'
|
||||
require 'zip'
|
||||
|
||||
Bundler.require
|
||||
Bundler.require :development if ENV['RACK_ENV'] == 'development'
|
||||
|
|
218
models/site.rb
218
models/site.rb
|
@ -1,6 +1,7 @@
|
|||
require 'tilt'
|
||||
require 'rss'
|
||||
require 'nokogiri'
|
||||
require 'pathname'
|
||||
|
||||
class Site < Sequel::Model
|
||||
include Sequel::ParanoidDelete
|
||||
|
@ -23,6 +24,7 @@ class Site < Sequel::Model
|
|||
image/x-icon
|
||||
application/pdf
|
||||
application/pgp-keys
|
||||
application/pgp
|
||||
text/xml
|
||||
application/xml
|
||||
audio/midi
|
||||
|
@ -70,6 +72,8 @@ class Site < Sequel::Model
|
|||
|
||||
EMAIL_SANITY_REGEX = /.+@.+\..+/i
|
||||
|
||||
EDITABLE_FILE_EXT = /html|htm|txt|js|css|md/i
|
||||
|
||||
BANNED_TIME = 2592000 # 30 days in seconds
|
||||
|
||||
TITLE_MAX = 100
|
||||
|
@ -197,16 +201,16 @@ class Site < Sequel::Model
|
|||
FileUtils.mkdir_p files_path
|
||||
|
||||
%w{index not_found}.each do |name|
|
||||
File.write file_path("#{name}.html"), render_template("#{name}.erb")
|
||||
File.write files_path("#{name}.html"), render_template("#{name}.erb")
|
||||
purge_cache "#{name}.html"
|
||||
ScreenshotWorker.perform_async values[:username], "#{name}.html"
|
||||
end
|
||||
|
||||
FileUtils.cp template_file_path('cat.png'), file_path('cat.png')
|
||||
FileUtils.cp template_file_path('cat.png'), files_path('cat.png')
|
||||
end
|
||||
|
||||
def get_file(filename)
|
||||
File.read file_path(filename)
|
||||
def get_file(path)
|
||||
File.read files_path(path)
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
|
@ -252,8 +256,8 @@ class Site < Sequel::Model
|
|||
FileUtils.mv files_path, File.join(PUBLIC_ROOT, 'banned_sites', username)
|
||||
}
|
||||
|
||||
site_files.file_list.collect {|f| f.filename}.each do |f|
|
||||
purge_cache f
|
||||
file_list.each do |path|
|
||||
purge_cache path
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -290,15 +294,11 @@ class Site < Sequel::Model
|
|||
!@blockings.select {|b| b.site_id == site.id}.empty?
|
||||
end
|
||||
|
||||
def self.valid_filename?(filename)
|
||||
return false if sanitize_filename(filename) != filename
|
||||
def self.valid_path?(path)
|
||||
puts 'ditto restrictions scrub'
|
||||
true
|
||||
end
|
||||
|
||||
def self.sanitize_filename(filename)
|
||||
filename.gsub(/[^a-zA-Z0-9_\-.]/, '')
|
||||
end
|
||||
|
||||
def self.valid_username?(username)
|
||||
!username.empty? && username.match(/^[a-zA-Z0-9_\-]+$/i)
|
||||
end
|
||||
|
@ -331,19 +331,21 @@ class Site < Sequel::Model
|
|||
true
|
||||
end
|
||||
|
||||
def purge_cache(filename)
|
||||
payload = {site: username, path: filename}
|
||||
def purge_cache(path)
|
||||
payload = {site: username, path: path}
|
||||
payload[:domain] = domain if !domain.empty?
|
||||
PurgeCacheWorker.perform_async payload
|
||||
end
|
||||
|
||||
def store_file(filename, uploaded)
|
||||
if File.exist?(file_path(filename)) &&
|
||||
Digest::SHA2.file(file_path(filename)).digest == Digest::SHA2.file(uploaded.path).digest
|
||||
def store_file(path, uploaded)
|
||||
path = files_path(path)
|
||||
if File.exist?(path) &&
|
||||
Digest::SHA2.file(path).digest == Digest::SHA2.file(uploaded.path).digest
|
||||
return false
|
||||
end
|
||||
|
||||
if filename == 'index.html'
|
||||
pathname = Pathname(path)
|
||||
if pathname.basename.to_s == 'index.html'
|
||||
new_title = Nokogiri::HTML(File.read(uploaded.path)).css('title').first.text
|
||||
|
||||
if new_title.length < TITLE_MAX
|
||||
|
@ -352,20 +354,26 @@ class Site < Sequel::Model
|
|||
end
|
||||
end
|
||||
|
||||
FileUtils.mv uploaded.path, file_path(filename)
|
||||
File.chmod(0640, file_path(filename))
|
||||
dirname = pathname.dirname.to_s
|
||||
|
||||
purge_cache filename
|
||||
|
||||
ext = File.extname(filename).gsub(/^./, '')
|
||||
|
||||
if ext.match HTML_REGEX
|
||||
ScreenshotWorker.perform_async values[:username], filename
|
||||
elsif ext.match IMAGE_REGEX
|
||||
ThumbnailWorker.perform_async values[:username], filename
|
||||
if !File.exists? dirname
|
||||
FileUtils.mkdir_p dirname
|
||||
end
|
||||
|
||||
SiteChange.record self, filename
|
||||
FileUtils.mv uploaded.path, path
|
||||
File.chmod 0640, path
|
||||
|
||||
purge_cache path
|
||||
|
||||
ext = File.extname(path).gsub(/^./, '')
|
||||
|
||||
if ext.match HTML_REGEX
|
||||
ScreenshotWorker.perform_async values[:username], path
|
||||
elsif ext.match IMAGE_REGEX
|
||||
ThumbnailWorker.perform_async values[:username], path
|
||||
end
|
||||
|
||||
SiteChange.record self, path
|
||||
|
||||
if self.site_changed != true
|
||||
self.site_changed = true
|
||||
|
@ -375,6 +383,20 @@ class Site < Sequel::Model
|
|||
true
|
||||
end
|
||||
|
||||
def is_directory?(path)
|
||||
File.directory? files_path(path)
|
||||
end
|
||||
|
||||
def create_directory(path)
|
||||
relative_path = files_path path
|
||||
if Dir.exists?(relative_path) || File.exist?(relative_path)
|
||||
return 'Directory (or file) already exists.'
|
||||
end
|
||||
|
||||
FileUtils.mkdir_p relative_path
|
||||
true
|
||||
end
|
||||
|
||||
def increment_changed_count
|
||||
self.changed_count += 1
|
||||
self.updated_at = Time.now
|
||||
|
@ -382,49 +404,59 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
def files_zip
|
||||
file_path = "/tmp/neocities-site-#{username}.zip"
|
||||
zip_name = "neocities-#{username}"
|
||||
|
||||
Zip::File.open(file_path, Zip::File::CREATE) do |zipfile|
|
||||
file_list.collect {|f| f.filename}.each do |filename|
|
||||
zipfile.add filename, file_path(filename)
|
||||
tmpfile = Tempfile.new 'neocities-site-zip'
|
||||
tmpfile.close
|
||||
|
||||
Zip::Archive.open(tmpfile.path, Zip::CREATE) do |ar|
|
||||
ar.add_dir(zip_name)
|
||||
|
||||
Dir.glob("#{base_files_path}/**/*").each do |path|
|
||||
relative_path = path.gsub(base_files_path+'/', '')
|
||||
|
||||
if File.directory?(path)
|
||||
ar.add_dir(zip_name+'/'+relative_path)
|
||||
else
|
||||
ar.add_file(zip_name+'/'+relative_path, path) # add_file(<entry name>, <source path>)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO Don't dump the zipfile into memory
|
||||
zipfile = File.read file_path
|
||||
File.delete file_path
|
||||
zipfile
|
||||
tmpfile.path
|
||||
end
|
||||
|
||||
def delete_file(filename)
|
||||
def delete_file(path)
|
||||
begin
|
||||
FileUtils.rm file_path(filename)
|
||||
FileUtils.rm files_path(path)
|
||||
rescue Errno::EISDIR
|
||||
FileUtils.remove_dir files_path(path), true
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
|
||||
purge_cache filename
|
||||
purge_cache path
|
||||
|
||||
ext = File.extname(filename).gsub(/^./, '')
|
||||
ext = File.extname(path).gsub(/^./, '')
|
||||
|
||||
screenshots_delete(filename) if ext.match HTML_REGEX
|
||||
thumbnails_delete(filename) if ext.match IMAGE_REGEX
|
||||
screenshots_delete(path) if ext.match HTML_REGEX
|
||||
thumbnails_delete(path) if ext.match IMAGE_REGEX
|
||||
|
||||
SiteChangeFile.filter(site_id: self.id, filename: filename).delete
|
||||
SiteChangeFile.filter(site_id: self.id, filename: path).delete
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def move_files_from(oldusername)
|
||||
FileUtils.mv files_path(oldusername), files_path
|
||||
FileUtils.mv base_files_path(oldusername), base_files_path
|
||||
end
|
||||
|
||||
def install_new_html_file(name)
|
||||
File.write file_path(name), render_template('index.erb')
|
||||
purge_cache name
|
||||
def install_new_html_file(path)
|
||||
File.write files_path(path), render_template('index.erb')
|
||||
purge_cache path
|
||||
end
|
||||
|
||||
def file_exists?(filename)
|
||||
File.exist? file_path(filename)
|
||||
def file_exists?(path)
|
||||
File.exist? files_path(path)
|
||||
end
|
||||
|
||||
def after_save
|
||||
|
@ -453,7 +485,7 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
# def after_destroy
|
||||
# FileUtils.rm_rf file_path
|
||||
# FileUtils.rm_rf files_path
|
||||
# super
|
||||
# end
|
||||
|
||||
|
@ -568,16 +600,48 @@ class Site < Sequel::Model
|
|||
File.join TEMPLATE_ROOT, name
|
||||
end
|
||||
|
||||
def files_path(name=nil)
|
||||
File.join SITE_FILES_ROOT, (name || username)
|
||||
def base_files_path(name=username)
|
||||
raise 'username missing' if name.nil? || name.empty?
|
||||
File.join SITE_FILES_ROOT, name
|
||||
end
|
||||
|
||||
def file_path(filename)
|
||||
File.join files_path, filename
|
||||
# https://practicingruby.com/articles/implementing-an-http-file-server?u=dc2ab0f9bb
|
||||
def scrubbed_path(path='')
|
||||
path ||= ''
|
||||
clean = []
|
||||
|
||||
parts = path.split '/'
|
||||
|
||||
parts.each do |part|
|
||||
next if part.empty? || part == '.'
|
||||
clean << part if part != '..'
|
||||
end
|
||||
|
||||
clean
|
||||
end
|
||||
|
||||
def file_list
|
||||
Dir.glob(File.join(files_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename}
|
||||
def files_path(path='')
|
||||
File.join base_files_path, scrubbed_path(path)
|
||||
end
|
||||
|
||||
def file_list(path='')
|
||||
list = Dir.glob(File.join(files_path(path), '*')).collect do |file_path|
|
||||
file = {
|
||||
path: file_path.gsub(base_files_path, ''),
|
||||
name: File.basename(file_path),
|
||||
ext: File.extname(file_path).gsub('.', ''),
|
||||
is_directory: File.directory?(file_path),
|
||||
is_root_index: file_path == "#{base_files_path}/index.html"
|
||||
}
|
||||
|
||||
file[:is_html] = !(file[:ext].match HTML_REGEX).nil?
|
||||
file[:is_image] = !(file[:ext].match IMAGE_REGEX).nil?
|
||||
file[:is_editable] = !(file[:ext].match EDITABLE_FILE_EXT).nil?
|
||||
file
|
||||
end
|
||||
|
||||
list.select {|f| f[:is_directory]}.sort_by {|f| f[:name]} +
|
||||
list.select {|f| f[:is_directory] == false}.sort_by{|f| f[:name]}
|
||||
end
|
||||
|
||||
def file_size_too_large?(size_in_bytes)
|
||||
|
@ -658,19 +722,19 @@ class Site < Sequel::Model
|
|||
values[:hits].to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
|
||||
end
|
||||
|
||||
def screenshots_delete(filename)
|
||||
def screenshots_delete(path)
|
||||
SCREENSHOT_RESOLUTIONS.each do |res|
|
||||
begin
|
||||
FileUtils.rm screenshot_path(filename, res)
|
||||
FileUtils.rm screenshot_path(path, res)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def thumbnails_delete(filename)
|
||||
def thumbnails_delete(path)
|
||||
THUMBNAIL_RESOLUTIONS.each do |res|
|
||||
begin
|
||||
FileUtils.rm thumbnail_path(filename, res)
|
||||
FileUtils.rm thumbnail_path(path, res)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
end
|
||||
|
@ -680,34 +744,34 @@ class Site < Sequel::Model
|
|||
Site.where(tags: tags).limit(limit, offset).order(:updated_at.desc).all
|
||||
end
|
||||
|
||||
def screenshot_path(filename, resolution)
|
||||
File.join(SCREENSHOTS_ROOT, values[:username], "#{filename}.#{resolution}.jpg")
|
||||
def screenshot_path(path, resolution)
|
||||
File.join(SCREENSHOTS_ROOT, values[:username], "#{path}.#{resolution}.jpg")
|
||||
end
|
||||
|
||||
def screenshot_exists?(filename, resolution)
|
||||
File.exist? File.join(SCREENSHOTS_ROOT, values[:username], "#{filename}.#{resolution}.jpg")
|
||||
def screenshot_exists?(path, resolution)
|
||||
File.exist? File.join(SCREENSHOTS_ROOT, values[:username], "#{path}.#{resolution}.jpg")
|
||||
end
|
||||
|
||||
def screenshot_url(filename, resolution)
|
||||
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.jpg"
|
||||
def screenshot_url(path, resolution)
|
||||
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{path}.#{resolution}.jpg"
|
||||
end
|
||||
|
||||
def thumbnail_path(filename, resolution)
|
||||
ext = File.extname(filename).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
File.join THUMBNAILS_ROOT, values[:username], "#{filename}.#{resolution}.#{ext}"
|
||||
def thumbnail_path(path, resolution)
|
||||
ext = File.extname(path).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
File.join THUMBNAILS_ROOT, values[:username], "#{path}.#{resolution}.#{ext}"
|
||||
end
|
||||
|
||||
def thumbnail_exists?(filename, resolution)
|
||||
File.exist? thumbnail_path(filename, resolution)
|
||||
def thumbnail_exists?(path, resolution)
|
||||
File.exist? thumbnail_path(path, resolution)
|
||||
end
|
||||
|
||||
def thumbnail_delete(filename, resolution)
|
||||
File.rm thumbnail_path(filename, resolution)
|
||||
def thumbnail_delete(path, resolution)
|
||||
File.rm thumbnail_path(path, resolution)
|
||||
end
|
||||
|
||||
def thumbnail_url(filename, resolution)
|
||||
ext = File.extname(filename).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
"#{THUMBNAILS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.#{ext}"
|
||||
def thumbnail_url(path, resolution)
|
||||
ext = File.extname(path).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
"#{THUMBNAILS_URL_ROOT}/#{values[:username]}/#{path}.#{resolution}.#{ext}"
|
||||
end
|
||||
|
||||
def to_rss
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
class SiteFile
|
||||
attr_reader :filename, :ext
|
||||
|
||||
def initialize(filename)
|
||||
@filename = filename
|
||||
@ext = File.extname(@filename).sub(/^./, '')
|
||||
end
|
||||
end
|
26
tests/acceptance/dashboard_tests.rb
Normal file
26
tests/acceptance/dashboard_tests.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
require_relative './environment.rb'
|
||||
|
||||
describe 'dashboard' do
|
||||
describe 'create directory' do
|
||||
|
||||
describe 'logged in' do
|
||||
|
||||
include Capybara::DSL
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
@site = Fabricate :site
|
||||
page.set_rack_session id: @site.id
|
||||
end
|
||||
|
||||
it 'creates a base directory' do
|
||||
visit '/dashboard'
|
||||
click_link 'New Folder'
|
||||
fill_in 'name', with: 'testimages'
|
||||
click_button 'Create'
|
||||
page.must_have_content /testimages/
|
||||
File.directory?(@site.files_path('testimages')).must_equal true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
tests/acceptance/environment.rb
Normal file
7
tests/acceptance/environment.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
require_relative '../environment'
|
||||
|
||||
Capybara.app = Sinatra::Application
|
||||
|
||||
def teardown
|
||||
Capybara.reset_sessions!
|
||||
end
|
11
tests/acceptance/index_tests.rb
Normal file
11
tests/acceptance/index_tests.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require_relative './environment.rb'
|
||||
|
||||
describe 'index' do
|
||||
include Capybara::DSL
|
||||
it 'goes to signup' do
|
||||
Capybara.reset_sessions!
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
page.must_have_content('Create a New Website')
|
||||
end
|
||||
end
|
44
tests/acceptance/settings_tests.rb
Normal file
44
tests/acceptance/settings_tests.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
require_relative './environment.rb'
|
||||
|
||||
describe 'site/settings' do
|
||||
describe 'change username' do
|
||||
include Capybara::DSL
|
||||
|
||||
def visit_signup
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
end
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for(:site)
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
fill_in 'email', with: @site[:email]
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
visit_signup
|
||||
end
|
||||
|
||||
it 'does not allow bad usernames' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
fill_in_valid
|
||||
click_button 'Create Home Page'
|
||||
visit '/settings'
|
||||
fill_in 'name', with: ''
|
||||
click_button 'Change Name'
|
||||
fill_in 'name', with: '../hack'
|
||||
click_button 'Change Name'
|
||||
fill_in 'name', with: 'derp../hack'
|
||||
click_button 'Change Name'
|
||||
## TODO fix this without screwing up legacy sites
|
||||
#fill_in 'name', with: '-'
|
||||
#click_button 'Change Name'
|
||||
page.must_have_content /valid.+name.+required/i
|
||||
Site[username: @site[:username]].wont_equal nil
|
||||
Site[username: ''].must_equal nil
|
||||
end
|
||||
end
|
||||
end
|
53
tests/acceptance/signin_tests.rb
Normal file
53
tests/acceptance/signin_tests.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
require_relative './environment.rb'
|
||||
|
||||
describe 'signin' do
|
||||
include Capybara::DSL
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for :site
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
end
|
||||
|
||||
def fill_in_valid_signup
|
||||
fill_in_valid
|
||||
fill_in 'email', with: @site[:email]
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
end
|
||||
|
||||
it 'fails for invalid login' do
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
page.must_have_content 'Welcome Back'
|
||||
fill_in_valid
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Invalid login'
|
||||
end
|
||||
|
||||
it 'fails for missing login' do
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
auth = {username: SecureRandom.hex, password: Faker::Internet.password}
|
||||
fill_in 'username', with: auth[:username]
|
||||
fill_in 'password', with: auth[:password]
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Invalid login'
|
||||
end
|
||||
|
||||
it 'logs in with proper credentials' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
fill_in_valid_signup
|
||||
click_button 'Create Home Page'
|
||||
Capybara.reset_sessions!
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Your Feed'
|
||||
end
|
||||
end
|
|
@ -1,61 +1,4 @@
|
|||
require_relative './environment'
|
||||
|
||||
Capybara.app = Sinatra::Application
|
||||
|
||||
def teardown
|
||||
Capybara.reset_sessions!
|
||||
Capybara.use_default_driver
|
||||
end
|
||||
|
||||
describe 'index' do
|
||||
include Capybara::DSL
|
||||
it 'goes to signup' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
page.must_have_content('Create a New Website')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'change username' do
|
||||
include Capybara::DSL
|
||||
|
||||
def visit_signup
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
end
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for(:site)
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
fill_in 'email', with: @site[:email]
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
visit_signup
|
||||
end
|
||||
|
||||
it 'does not allow bad usernames' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
fill_in_valid
|
||||
click_button 'Create Home Page'
|
||||
visit '/settings'
|
||||
fill_in 'name', with: ''
|
||||
click_button 'Change Name'
|
||||
fill_in 'name', with: '../hack'
|
||||
click_button 'Change Name'
|
||||
fill_in 'name', with: 'derp../hack'
|
||||
click_button 'Change Name'
|
||||
## TODO fix this without screwing up legacy sites
|
||||
#fill_in 'name', with: '-'
|
||||
#click_button 'Change Name'
|
||||
page.must_have_content /valid.+name.+required/i
|
||||
Site[username: @site[:username]].wont_equal nil
|
||||
Site[username: ''].must_equal nil
|
||||
end
|
||||
end
|
||||
require_relative './environment.rb'
|
||||
|
||||
describe 'signup' do
|
||||
include Capybara::DSL
|
||||
|
@ -215,56 +158,4 @@ describe 'signup' do
|
|||
page.must_have_content 'Your Feed'
|
||||
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'signin' do
|
||||
include Capybara::DSL
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for :site
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
end
|
||||
|
||||
def fill_in_valid_signup
|
||||
fill_in_valid
|
||||
fill_in 'email', with: @site[:email]
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
end
|
||||
|
||||
it 'fails for invalid login' do
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
page.must_have_content 'Welcome Back'
|
||||
fill_in_valid
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Invalid login'
|
||||
end
|
||||
|
||||
it 'fails for missing login' do
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
auth = {username: SecureRandom.hex, password: Faker::Internet.password}
|
||||
fill_in 'username', with: auth[:username]
|
||||
fill_in 'password', with: auth[:password]
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Invalid login'
|
||||
end
|
||||
|
||||
it 'logs in with proper credentials' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
fill_in_valid_signup
|
||||
click_button 'Create Home Page'
|
||||
Capybara.reset_sessions!
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Your Feed'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,17 +84,17 @@ describe 'api delete' do
|
|||
res[:error_type].must_equal 'cannot_delete_index'
|
||||
end
|
||||
|
||||
it 'fails with bad filename' do
|
||||
it 'succeeds with weird filenames' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@site.store_file 't$st.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
post '/api/delete', filenames: ['t$st.jpg']
|
||||
res[:error_type].must_equal 'bad_filename'
|
||||
res[:result].must_equal 'success'
|
||||
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/delete', filenames: ['./config.yml']
|
||||
res[:error_type].must_equal 'bad_filename'
|
||||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails with missing files' do
|
||||
|
@ -137,13 +137,59 @@ describe 'api upload' do
|
|||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails for invalid filenames' do
|
||||
it 'resists directory traversal attack' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'../lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'bad_filename'
|
||||
res[:result].must_equal 'success'
|
||||
File.exist?(File.join(Site::SITE_FILES_ROOT, @site.username, 'lol.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'scrubs root path slash' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'/lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:result].must_equal 'success'
|
||||
File.exist?(File.join(Site::SITE_FILES_ROOT, @site.username, 'lol.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'fails for missing file name' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'/' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'invalid_file_type'
|
||||
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails for file with no extension' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'derpie' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'invalid_file_type'
|
||||
end
|
||||
|
||||
it 'creates path for file uploads' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'derpie/derpingtons/lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:result].must_equal 'success'
|
||||
File.exist?(@site.files_path('derpie/derpingtons/lol.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'fails for invalid files' do
|
||||
|
@ -180,7 +226,7 @@ describe 'api upload' do
|
|||
end
|
||||
|
||||
def site_file_exists?(file)
|
||||
File.exist?(@site.file_path('test.jpg'))
|
||||
File.exist?(@site.files_path('test.jpg'))
|
||||
end
|
||||
|
||||
def res
|
||||
|
|
|
@ -9,18 +9,23 @@ end
|
|||
|
||||
SimpleCov.command_name 'minitest'
|
||||
|
||||
require 'rack_session_access'
|
||||
require './environment'
|
||||
require 'webmock'
|
||||
include WebMock::API
|
||||
require './app'
|
||||
|
||||
Bundler.require :test
|
||||
|
||||
#require 'minitest/pride'
|
||||
require 'minitest/autorun'
|
||||
|
||||
require 'sidekiq/testing'
|
||||
|
||||
Sinatra::Application.configure do |app|
|
||||
app.use RackSessionAccess::Middleware
|
||||
end
|
||||
|
||||
require 'capybara/poltergeist'
|
||||
require 'rack_session_access/capybara'
|
||||
|
||||
Site.bcrypt_cost = BCrypt::Engine::MIN_COST
|
||||
|
||||
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
|
||||
|
|
33
tests/site_file_tests.rb
Normal file
33
tests/site_file_tests.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
require_relative './environment.rb'
|
||||
require 'rack/test'
|
||||
|
||||
include Rack::Test::Methods
|
||||
|
||||
def app
|
||||
Sinatra::Application
|
||||
end
|
||||
|
||||
describe 'site_files' do
|
||||
describe 'upload' do
|
||||
it 'succeeds with valid file' do
|
||||
site = Fabricate :site
|
||||
post '/site_files/upload', {
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
|
||||
'csrf_token' => 'abcd'
|
||||
}, {'rack.session' => { 'id' => site.id, '_csrf_token' => 'abcd' }}
|
||||
last_response.body.must_match /successfully uploaded/i
|
||||
File.exists?(site.files_path('test.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'works with directory path' do
|
||||
site = Fabricate :site
|
||||
post '/site_files/upload', {
|
||||
'dir' => 'derpie/derptest',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg'),
|
||||
'csrf_token' => 'abcd'
|
||||
}, {'rack.session' => { 'id' => site.id, '_csrf_token' => 'abcd' }}
|
||||
last_response.body.must_match /successfully uploaded/i
|
||||
File.exists?(site.files_path('derpie/derptest/test.jpg')).must_equal true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,14 +15,6 @@
|
|||
display: none;
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
<form action="/site_files/upload" class="dropzone" id="uploads">
|
||||
<input name="csrf_token" type="hidden" value="#{csrf_token}">
|
||||
</form>
|
||||
|
||||
|
||||
<div id="upload_status" style="font-size: 16pt"></div>
|
||||
-->
|
||||
|
||||
<div class="header-Outro with-site-image">
|
||||
<div class="row content wide">
|
||||
|
@ -88,10 +80,27 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="breadcrumbs">Home</div>
|
||||
<div class="breadcrumbs">
|
||||
<% if params[:dir].nil? %>
|
||||
Home
|
||||
<% else %>
|
||||
<a href="?">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 %>"><%= dir %></a> /
|
||||
<% else %>
|
||||
<%= dir %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a href="/site_files/new_page" class="btn-Action new-Page"><span>New Page</span></a>
|
||||
<a href="" class="btn-Action new-Folder"><span>New Folder</span></a>
|
||||
<a href="#createDir" class="btn-Action new-Folder" data-toggle="modal"><span>New Folder</span></a>
|
||||
<a href="#" class="btn-Action upload" onclick="clickUploadFiles(); return false"><span>Upload</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -99,47 +108,49 @@
|
|||
<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 %>">
|
||||
<div class="upload-Boundary <%= current_site.file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||
<% current_site.file_list.each do |file| %>
|
||||
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||
<% @file_list.each do |file| %>
|
||||
<div class="file filehover">
|
||||
<% if file.ext.match(Site::HTML_REGEX) && current_site.screenshot_exists?(file.filename, '105x63') %>
|
||||
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '105x63') %>
|
||||
<div class="html-thumbnail html fileimagehover">
|
||||
<img src="<%= current_site.screenshot_url(file.filename, '105x63') %>">
|
||||
<img src="<%= current_site.screenshot_url(file[:path], '105x63') %>">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<% elsif file.ext.match(Site::IMAGE_REGEX) && current_site.thumbnail_exists?(file.filename, '105x63') %>
|
||||
<% elsif file[:is_image] && current_site.thumbnail_exists?(file[:path], '105x63') %>
|
||||
<div class="html-thumbnail image fileimagehover">
|
||||
<img src="<%= current_site.thumbnail_url(file.filename, '105x63') %>" style="">
|
||||
<img src="<%= current_site.thumbnail_url(file[:path], '105x63') %>" style="">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<!-- if folder
|
||||
<% elsif file[:is_directory] %>
|
||||
<div class="html-thumbnail folder fileimagehover">
|
||||
<div class="folder-icon"></div>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
-->
|
||||
<% else %>
|
||||
<div class="html-thumbnail misc fileimagehover">
|
||||
<div class="misc-icon"><%= file.ext %></div>
|
||||
<div class="misc-icon"><%= file[:ext] %></div>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<a class="title">
|
||||
<% if file.filename.length > 14 %>
|
||||
<%= file.filename.slice(0..14) %>…
|
||||
<% if file[:name].length > 15 %>
|
||||
<%= file[:name].slice(0..14) %>…
|
||||
<% else %>
|
||||
<%= file.filename %>
|
||||
<%= file[:name] %>
|
||||
<% end %>
|
||||
</a>
|
||||
<div class="overlay">
|
||||
<% if file.ext.match(/html|htm|txt|js|css|md/) %>
|
||||
<a href="/site_files/text_editor/<%= file.filename %>"><i class="icon-edit" title="Edit"> Edit</i></a>
|
||||
<% if file[:is_editable] %>
|
||||
<a href="/site_files/text_editor<%= file[:path] %>"><i class="icon-edit" title="Edit"> Edit</i></a>
|
||||
<% end %>
|
||||
<% if file.filename != 'index.html' %>
|
||||
<a href="#" onclick="confirmFileDelete('<%= file.filename %>')"><i class="icon-trash" title="Delete"> Delete</i></a>
|
||||
<% if file[:is_directory] %>
|
||||
<a href="?dir=<%= Rack::Utils.escape file[:path] %>"><i class="icon-edit" title="Manage"> Manage</i></a>
|
||||
<% end %>
|
||||
<a class="link-overlay" href="http://<%= current_site.username %>.neocities.org/<%= file.filename %>" title="View <%= file.filename %>" target="_blank"></a>
|
||||
<% if !file[:is_root_index] %>
|
||||
<a href="#" onclick="confirmFileDelete('<%= file[:path] %>')"><i class="icon-trash" title="Delete"> Delete</i></a>
|
||||
<% end %>
|
||||
<a class="link-overlay" href="http://<%= current_site.username %>.neocities.org<%= file[:path] %>" title="View <%= file[:path] %>" target="_blank"></a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -158,7 +169,7 @@
|
|||
<h3 id="deleteConfirmModalLabel">Confirm deletion</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete the file <span id="deleteFileName"></span>. Are you sure?</p>
|
||||
<p>You are about to delete "<span id="deleteFileName"></span>". Are you sure?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
|
@ -174,9 +185,10 @@
|
|||
</div>
|
||||
</main>
|
||||
|
||||
<form id="uploadFilesButtonForm" method="POST" action="/site_files/upload" enctype=multipart/form-data style="display: none" onsubmit="showUploadProgress()">
|
||||
<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>
|
||||
|
||||
|
@ -231,3 +243,24 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">x</button>
|
||||
<h3 id="createDirLabel">Create Folder</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Folder name:
|
||||
</p>
|
||||
<input name="name" type="text" placeholder="images">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-Action">Create</button>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
Loading…
Add table
Reference in a new issue