mirror of
https://github.com/neocities/neocities.git
synced 2025-04-28 11:12:30 +02:00
remove neo.css, it is autogeneratedn ow
This commit is contained in:
commit
c53e1bdb22
31 changed files with 730 additions and 3475 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -18,9 +18,13 @@ doc/
|
||||||
tests/coverage
|
tests/coverage
|
||||||
config.yml
|
config.yml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
public/assets/css/.sass-cache/
|
public/assets/css/neo.css
|
||||||
public/site_thumbnails
|
public/site_thumbnails
|
||||||
public/sites
|
public/sites
|
||||||
public/site_screenshots
|
public/site_screenshots
|
||||||
|
public/site_screenshots_test
|
||||||
|
public/site_thumbnails_test
|
||||||
*.swp
|
*.swp
|
||||||
files/map.txt
|
files/map.txt
|
||||||
|
.sass-cache
|
||||||
|
.sass-cache/*
|
||||||
|
|
10
Gemfile
10
Gemfile
|
@ -7,7 +7,6 @@ gem 'bcrypt'
|
||||||
gem 'sinatra-flash', require: 'sinatra/flash'
|
gem 'sinatra-flash', require: 'sinatra/flash'
|
||||||
gem 'sinatra-xsendfile', require: 'sinatra/xsendfile'
|
gem 'sinatra-xsendfile', require: 'sinatra/xsendfile'
|
||||||
gem 'puma', require: nil
|
gem 'puma', require: nil
|
||||||
gem 'rubyzip', require: 'zip'
|
|
||||||
gem 'rack-recaptcha', require: 'rack/recaptcha'
|
gem 'rack-recaptcha', require: 'rack/recaptcha'
|
||||||
gem 'rmagick', require: nil
|
gem 'rmagick', require: nil
|
||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
|
@ -19,6 +18,10 @@ gem 'erubis'
|
||||||
gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'
|
gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'
|
||||||
gem 'screencap'
|
gem 'screencap'
|
||||||
gem 'cocaine'
|
gem 'cocaine'
|
||||||
|
gem 'zipruby'
|
||||||
|
gem 'always_verify_ssl_certificates'
|
||||||
|
gem 'sass', require: nil
|
||||||
|
gem 'dav4rack'
|
||||||
|
|
||||||
platform :mri do
|
platform :mri do
|
||||||
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
||||||
|
@ -45,7 +48,6 @@ end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'shotgun', require: nil
|
gem 'shotgun', require: nil
|
||||||
gem 'sass', require: nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
@ -54,13 +56,13 @@ group :test do
|
||||||
gem 'minitest'
|
gem 'minitest'
|
||||||
gem 'minitest-reporters', require: 'minitest/reporters'
|
gem 'minitest-reporters', require: 'minitest/reporters'
|
||||||
gem 'rack-test', require: 'rack/test'
|
gem 'rack-test', require: 'rack/test'
|
||||||
gem 'webmock'
|
|
||||||
gem 'mocha', require: nil
|
gem 'mocha', require: nil
|
||||||
gem 'rake', require: nil
|
gem 'rake', require: nil
|
||||||
gem 'poltergeist'
|
gem 'poltergeist'
|
||||||
gem 'phantomjs', require: 'phantomjs/poltergeist'
|
gem 'phantomjs', require: 'phantomjs/poltergeist'
|
||||||
gem 'capybara'
|
|
||||||
gem 'capybara_minitest_spec'
|
gem 'capybara_minitest_spec'
|
||||||
|
gem 'rack_session_access', require: nil
|
||||||
|
gem 'webmock', require: nil
|
||||||
|
|
||||||
platform :mri do
|
platform :mri do
|
||||||
gem 'simplecov', require: nil
|
gem 'simplecov', require: nil
|
||||||
|
|
34
Gemfile.lock
34
Gemfile.lock
|
@ -18,6 +18,7 @@ GEM
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.3.6)
|
addressable (2.3.6)
|
||||||
ago (0.1.5)
|
ago (0.1.5)
|
||||||
|
always_verify_ssl_certificates (0.3.0)
|
||||||
ansi (1.4.3)
|
ansi (1.4.3)
|
||||||
autoparse (0.3.3)
|
autoparse (0.3.3)
|
||||||
addressable (>= 2.3.1)
|
addressable (>= 2.3.1)
|
||||||
|
@ -25,7 +26,7 @@ GEM
|
||||||
multi_json (>= 1.0.0)
|
multi_json (>= 1.0.0)
|
||||||
bcrypt (3.1.7)
|
bcrypt (3.1.7)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
capybara (2.2.1)
|
capybara (2.4.1)
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
|
@ -42,16 +43,20 @@ GEM
|
||||||
cocaine (0.5.4)
|
cocaine (0.5.4)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.0)
|
coderay (1.1.0)
|
||||||
columnize (0.3.6)
|
columnize (0.8.9)
|
||||||
connection_pool (2.0.0)
|
connection_pool (2.0.0)
|
||||||
crack (0.4.2)
|
crack (0.4.2)
|
||||||
safe_yaml (~> 1.0.0)
|
safe_yaml (~> 1.0.0)
|
||||||
|
dav4rack (0.3.0)
|
||||||
|
nokogiri (>= 1.4.2)
|
||||||
|
rack (>= 1.1.0)
|
||||||
|
uuidtools (~> 2.1.1)
|
||||||
debugger (1.6.6)
|
debugger (1.6.6)
|
||||||
columnize (>= 0.3.1)
|
columnize (>= 0.3.1)
|
||||||
debugger-linecache (~> 1.2.0)
|
debugger-linecache (~> 1.2.0)
|
||||||
debugger-ruby_core_source (~> 1.3.2)
|
debugger-ruby_core_source (~> 1.3.2)
|
||||||
debugger-linecache (1.2.0)
|
debugger-linecache (1.2.0)
|
||||||
debugger-ruby_core_source (1.3.2)
|
debugger-ruby_core_source (1.3.5)
|
||||||
docile (1.1.3)
|
docile (1.1.3)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
extlib (0.9.16)
|
extlib (0.9.16)
|
||||||
|
@ -89,7 +94,7 @@ GEM
|
||||||
metaclass (0.0.4)
|
metaclass (0.0.4)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (1.25.1)
|
mime-types (1.25.1)
|
||||||
mini_portile (0.5.3)
|
mini_portile (0.6.0)
|
||||||
minitest (5.3.1)
|
minitest (5.3.1)
|
||||||
minitest-reporters (1.0.2)
|
minitest-reporters (1.0.2)
|
||||||
ansi
|
ansi
|
||||||
|
@ -98,13 +103,13 @@ GEM
|
||||||
powerbar
|
powerbar
|
||||||
mocha (1.0.0)
|
mocha (1.0.0)
|
||||||
metaclass (~> 0.0.1)
|
metaclass (~> 0.0.1)
|
||||||
multi_json (1.9.2)
|
multi_json (1.10.1)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nokogiri (1.6.1)
|
nokogiri (1.6.3.1)
|
||||||
mini_portile (~> 0.5.0)
|
mini_portile (= 0.6.0)
|
||||||
pg (0.17.1)
|
pg (0.17.1)
|
||||||
phantomjs (1.9.7.0)
|
phantomjs (1.9.7.0)
|
||||||
poltergeist (1.5.0)
|
poltergeist (1.5.1)
|
||||||
capybara (~> 2.1)
|
capybara (~> 2.1)
|
||||||
cliver (~> 0.3.1)
|
cliver (~> 0.3.1)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
|
@ -129,6 +134,9 @@ GEM
|
||||||
json
|
json
|
||||||
rack-test (0.6.2)
|
rack-test (0.6.2)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
|
rack_session_access (0.1.1)
|
||||||
|
builder (>= 2.0.0)
|
||||||
|
rack (>= 1.0.0)
|
||||||
rainbows (4.6.1)
|
rainbows (4.6.1)
|
||||||
kgio (~> 2.5)
|
kgio (~> 2.5)
|
||||||
rack (~> 1.1)
|
rack (~> 1.1)
|
||||||
|
@ -142,7 +150,6 @@ GEM
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
retriable (1.4.1)
|
retriable (1.4.1)
|
||||||
rmagick (2.13.2)
|
rmagick (2.13.2)
|
||||||
rubyzip (1.1.2)
|
|
||||||
safe_yaml (1.0.1)
|
safe_yaml (1.0.1)
|
||||||
sass (3.3.8)
|
sass (3.3.8)
|
||||||
screencap (0.1.1)
|
screencap (0.1.1)
|
||||||
|
@ -194,19 +201,21 @@ GEM
|
||||||
webmock (1.17.4)
|
webmock (1.17.4)
|
||||||
addressable (>= 2.2.7)
|
addressable (>= 2.2.7)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
websocket-driver (0.3.2)
|
websocket-driver (0.3.4)
|
||||||
xpath (2.0.0)
|
xpath (2.0.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
|
zipruby (0.3.6)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
ago
|
ago
|
||||||
|
always_verify_ssl_certificates
|
||||||
bcrypt
|
bcrypt
|
||||||
capybara
|
|
||||||
capybara_minitest_spec
|
capybara_minitest_spec
|
||||||
cocaine
|
cocaine
|
||||||
|
dav4rack
|
||||||
erubis
|
erubis
|
||||||
fabrication
|
fabrication
|
||||||
faker
|
faker
|
||||||
|
@ -228,12 +237,12 @@ DEPENDENCIES
|
||||||
puma
|
puma
|
||||||
rack-recaptcha
|
rack-recaptcha
|
||||||
rack-test
|
rack-test
|
||||||
|
rack_session_access
|
||||||
rainbows
|
rainbows
|
||||||
rake
|
rake
|
||||||
redis
|
redis
|
||||||
rmagick
|
rmagick
|
||||||
ruby-debug
|
ruby-debug
|
||||||
rubyzip
|
|
||||||
sass
|
sass
|
||||||
screencap
|
screencap
|
||||||
sequel (= 4.8.0)
|
sequel (= 4.8.0)
|
||||||
|
@ -247,3 +256,4 @@ DEPENDENCIES
|
||||||
stripe!
|
stripe!
|
||||||
tilt
|
tilt
|
||||||
webmock
|
webmock
|
||||||
|
zipruby
|
||||||
|
|
4
Rakefile
4
Rakefile
|
@ -7,7 +7,7 @@ end
|
||||||
desc "Run all tests"
|
desc "Run all tests"
|
||||||
Rake::TestTask.new do |t|
|
Rake::TestTask.new do |t|
|
||||||
t.libs << "spec"
|
t.libs << "spec"
|
||||||
t.test_files = FileList['tests/*_tests.rb']
|
t.test_files = FileList['tests/**/*_tests.rb']
|
||||||
t.verbose = true
|
t.verbose = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -87,4 +87,4 @@ end
|
||||||
desc 'Compile domain map for nginx'
|
desc 'Compile domain map for nginx'
|
||||||
task :compile_domain_map => [:environment] do
|
task :compile_domain_map => [:environment] do
|
||||||
File.open('./files/map.txt', 'w'){|f| Site.exclude(domain: nil).exclude(domain: '').select(:username,:domain).all.collect {|s| f.write "#{s.domain} #{s.username};\n" }}
|
File.open('./files/map.txt', 'w'){|f| Site.exclude(domain: nil).exclude(domain: '').select(:username,:domain).all.collect {|s| f.write "#{s.domain} #{s.username};\n" }}
|
||||||
end
|
end
|
||||||
|
|
95
app.rb
95
app.rb
|
@ -452,6 +452,17 @@ end
|
||||||
|
|
||||||
get '/dashboard' do
|
get '/dashboard' do
|
||||||
require_login
|
require_login
|
||||||
|
|
||||||
|
if params[:dir] && params[:dir][0] != '/'
|
||||||
|
params[:dir] = '/'+params[:dir]
|
||||||
|
end
|
||||||
|
|
||||||
|
if !File.directory?(current_site.files_path(params[:dir]))
|
||||||
|
redirect '/dashboard'
|
||||||
|
end
|
||||||
|
|
||||||
|
@dir = params[:dir]
|
||||||
|
@file_list = current_site.file_list @dir
|
||||||
erb :'dashboard'
|
erb :'dashboard'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -562,7 +573,7 @@ post '/change_name' do
|
||||||
end
|
end
|
||||||
|
|
||||||
old_host = current_site.host
|
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]
|
current_site.username = params[:name]
|
||||||
|
|
||||||
|
@ -629,7 +640,8 @@ def file_upload_response(error=nil)
|
||||||
@error = error
|
@error = error
|
||||||
halt 200, erb(:'dashboard')
|
halt 200, erb(:'dashboard')
|
||||||
else
|
else
|
||||||
redirect '/dashboard'
|
query_string = params[:dir] ? "?"+Rack::Utils.build_query(dir: params[:dir]) : ''
|
||||||
|
redirect "/dashboard#{query_string}"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
halt http_error_code, error if error
|
halt http_error_code, error if error
|
||||||
|
@ -637,6 +649,20 @@ def file_upload_response(error=nil)
|
||||||
end
|
end
|
||||||
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
|
post '/site_files/upload' do
|
||||||
require_login
|
require_login
|
||||||
@errors = []
|
@errors = []
|
||||||
|
@ -647,12 +673,12 @@ post '/site_files/upload' do
|
||||||
end
|
end
|
||||||
|
|
||||||
params[:files].each do |file|
|
params[:files].each do |file|
|
||||||
|
file[:filename] = "#{params[:dir]}/#{file[:filename]}" if params[:dir]
|
||||||
if current_site.file_size_too_large? file[:tempfile].size
|
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
|
end
|
||||||
|
|
||||||
if !Site.valid_file_type? file
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -664,9 +690,8 @@ post '/site_files/upload' do
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
params[:files].each do |file|
|
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
|
end
|
||||||
|
|
||||||
current_site.increment_changed_count if results.include?(true)
|
current_site.increment_changed_count if results.include?(true)
|
||||||
|
|
||||||
file_upload_response
|
file_upload_response
|
||||||
|
@ -674,20 +699,18 @@ end
|
||||||
|
|
||||||
post '/site_files/delete' do
|
post '/site_files/delete' do
|
||||||
require_login
|
require_login
|
||||||
sanitized_filename = Site.sanitize_filename params[:filename]
|
current_site.delete_file params[:filename]
|
||||||
|
|
||||||
current_site.delete_file(sanitized_filename)
|
flash[:success] = "Deleted #{params[:filename]}."
|
||||||
|
|
||||||
flash[:success] = "Deleted file #{params[:filename]}."
|
|
||||||
redirect '/dashboard'
|
redirect '/dashboard'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/site_files/:username.zip' do |username|
|
get '/site_files/:username.zip' do |username|
|
||||||
require_login
|
require_login
|
||||||
zipfile = current_site.files_zip
|
zipfile_path = current_site.files_zip
|
||||||
content_type 'application/octet-stream'
|
content_type 'application/octet-stream'
|
||||||
attachment "#{current_site.username}.zip"
|
attachment "neocities-#{current_site.username}.zip"
|
||||||
zipfile
|
send_file zipfile_path
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/site_files/download/:filename' do |filename|
|
get '/site_files/download/:filename' do |filename|
|
||||||
|
@ -697,10 +720,11 @@ get '/site_files/download/:filename' do |filename|
|
||||||
current_site.get_file filename
|
current_site.get_file filename
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/site_files/text_editor/:filename' do |filename|
|
get %r{\/site_files\/text_editor\/(.+)} do
|
||||||
require_login
|
require_login
|
||||||
|
@filename = params[:captures].first
|
||||||
begin
|
begin
|
||||||
@file_data = current_site.get_file filename
|
@file_data = current_site.get_file @filename
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
flash[:error] = 'We could not find the requested file.'
|
flash[:error] = 'We could not find the requested file.'
|
||||||
redirect '/dashboard'
|
redirect '/dashboard'
|
||||||
|
@ -708,8 +732,9 @@ get '/site_files/text_editor/:filename' do |filename|
|
||||||
erb :'site_files/text_editor'
|
erb :'site_files/text_editor'
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/site_files/save/:filename' do |filename|
|
post %r{\/site_files\/save\/(.+)} do
|
||||||
require_login_ajax
|
require_login_ajax
|
||||||
|
filename = params[:captures].first
|
||||||
|
|
||||||
tempfile = Tempfile.new 'neocities_saving_file'
|
tempfile = Tempfile.new 'neocities_saving_file'
|
||||||
|
|
||||||
|
@ -722,9 +747,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.'
|
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
|
end
|
||||||
|
|
||||||
sanitized_filename = Site.sanitize_filename filename
|
current_site.store_file filename, tempfile
|
||||||
|
|
||||||
current_site.store_file sanitized_filename, tempfile
|
|
||||||
|
|
||||||
'ok'
|
'ok'
|
||||||
end
|
end
|
||||||
|
@ -937,14 +960,11 @@ end
|
||||||
|
|
||||||
post '/api/upload' do
|
post '/api/upload' do
|
||||||
require_api_credentials
|
require_api_credentials
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
params.each do |k,v|
|
params.each do |k,v|
|
||||||
next unless v.is_a?(Hash) && v[:tempfile]
|
next unless v.is_a?(Hash) && v[:tempfile]
|
||||||
filename = k.to_s
|
path = k.to_s
|
||||||
api_error(400, 'bad_filename', "#{filename} is not a valid filename, files not uploaded") unless Site.valid_filename? filename
|
files << {filename: k || v[:filename], tempfile: v[:tempfile]}
|
||||||
files << {filename: filename, tempfile: v[:tempfile]}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
api_error 400, 'missing_files', 'you must provide files to upload' if files.empty?
|
api_error 400, 'missing_files', 'you must provide files to upload' if files.empty?
|
||||||
|
@ -959,6 +979,10 @@ post '/api/upload' do
|
||||||
if !Site.valid_file_type?(file)
|
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"
|
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
|
end
|
||||||
|
|
||||||
|
if File.directory? file[:filename]
|
||||||
|
api_error 400, 'directory_exists', 'this name is being used by a directory, cannot continue'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
@ -976,26 +1000,25 @@ post '/api/delete' do
|
||||||
|
|
||||||
api_error 400, 'missing_filenames', 'you must provide files to delete' if params[:filenames].nil? || params[:filenames].empty?
|
api_error 400, 'missing_filenames', 'you must provide files to delete' if params[:filenames].nil? || params[:filenames].empty?
|
||||||
|
|
||||||
filenames = []
|
paths = []
|
||||||
|
params[:filenames].each do |path|
|
||||||
params[:filenames].each do |filename|
|
unless path.is_a?(String)
|
||||||
unless filename.is_a?(String) && Site.valid_filename?(filename)
|
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
||||||
api_error 400, 'bad_filename', "#{filename} is not a valid filename, canceled deleting"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if !current_site.file_exists?(filename)
|
if !current_site.file_exists?(path)
|
||||||
api_error 400, 'missing_files', "#{filename} was not found on your site, canceled deleting"
|
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
||||||
end
|
end
|
||||||
|
|
||||||
if filename == 'index.html'
|
if path == 'index.html'
|
||||||
api_error 400, 'cannot_delete_index', 'you cannot delete your index.html file, canceled deleting'
|
api_error 400, 'cannot_delete_index', 'you cannot delete your index.html file, canceled deleting'
|
||||||
end
|
end
|
||||||
|
|
||||||
filenames << filename
|
paths << path
|
||||||
end
|
end
|
||||||
|
|
||||||
filenames.each do |filename|
|
paths.each do |path|
|
||||||
current_site.delete_file(filename)
|
current_site.delete_file(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
api_success 'file(s) have been deleted'
|
api_success 'file(s) have been deleted'
|
||||||
|
|
58
config.ru
58
config.ru
|
@ -1,8 +1,64 @@
|
||||||
|
require 'rubygems'
|
||||||
require './app.rb'
|
require './app.rb'
|
||||||
require 'sidekiq/web'
|
require 'sidekiq/web'
|
||||||
|
|
||||||
map('/') { run Sinatra::Application }
|
map('/') { run Sinatra::Application }
|
||||||
|
|
||||||
|
map '/webdav' do
|
||||||
|
|
||||||
|
use Rack::Auth::Basic do |username, password|
|
||||||
|
Site.valid_login? username, password
|
||||||
|
end
|
||||||
|
|
||||||
|
run lambda {|env|
|
||||||
|
site = Site[username: env['REMOTE_USER']]
|
||||||
|
|
||||||
|
if env['REQUEST_METHOD'] == 'PUT'
|
||||||
|
path = env['PATH_INFO']
|
||||||
|
tmpfile = Tempfile.new 'davfile', encoding: 'binary'
|
||||||
|
tmpfile.write env['rack.input'].read
|
||||||
|
tmpfile.close
|
||||||
|
|
||||||
|
if site.file_size_too_large? tmpfile.size
|
||||||
|
return [507, {}, ['']]
|
||||||
|
end
|
||||||
|
|
||||||
|
if Site.valid_file_type?(filename: path, tempfile: tmpfile)
|
||||||
|
site.store_file path, tmpfile
|
||||||
|
return [201, {}, ['']]
|
||||||
|
else
|
||||||
|
return [415, {}, ['']]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if env['REQUEST_METHOD'] == 'MOVE'
|
||||||
|
tmpfile = Tempfile.new 'moved_file'
|
||||||
|
tmpfile.close
|
||||||
|
|
||||||
|
destination = env['HTTP_DESTINATION'].match(/^.+\/webdav(.+)$/i).captures.first
|
||||||
|
|
||||||
|
FileUtils.cp site.files_path(env['PATH_INFO']), tmpfile.path
|
||||||
|
|
||||||
|
DB.transaction do
|
||||||
|
site.store_file destination, tmpfile
|
||||||
|
site.delete_file env['PATH_INFO']
|
||||||
|
end
|
||||||
|
|
||||||
|
return [201, {}, ['']]
|
||||||
|
end
|
||||||
|
|
||||||
|
if env['REQUEST_METHOD'] == 'DELETE'
|
||||||
|
site.delete_file env['PATH_INFO']
|
||||||
|
return [201, {}, ['']]
|
||||||
|
end
|
||||||
|
|
||||||
|
res = DAV4Rack::Handler.new(
|
||||||
|
root: Site.select(:username).where(username: env['REMOTE_USER']).first.files_path,
|
||||||
|
root_uri_path: '/webdav'
|
||||||
|
).call(env)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
map '/sidekiq' do
|
map '/sidekiq' do
|
||||||
use Rack::Auth::Basic, "Protected Area" do |username, password|
|
use Rack::Auth::Basic, "Protected Area" do |username, password|
|
||||||
raise 'missing sidekiq auth' unless $config['sidekiq_user'] && $config['sidekiq_pass']
|
raise 'missing sidekiq auth' unless $config['sidekiq_user'] && $config['sidekiq_pass']
|
||||||
|
@ -10,4 +66,4 @@ map '/sidekiq' do
|
||||||
end
|
end
|
||||||
|
|
||||||
run Sidekiq::Web
|
run Sidekiq::Web
|
||||||
end
|
end
|
|
@ -7,7 +7,6 @@ Encoding.default_external = 'UTF-8'
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'logger'
|
require 'logger'
|
||||||
require 'zip'
|
|
||||||
|
|
||||||
Bundler.require
|
Bundler.require
|
||||||
Bundler.require :development if ENV['RACK_ENV'] == 'development'
|
Bundler.require :development if ENV['RACK_ENV'] == 'development'
|
||||||
|
@ -28,8 +27,6 @@ end
|
||||||
DB = Sequel.connect $config['database'], sslmode: 'disable', max_connections: $config['database_pool']
|
DB = Sequel.connect $config['database'], sslmode: 'disable', max_connections: $config['database_pool']
|
||||||
DB.extension :pagination
|
DB.extension :pagination
|
||||||
|
|
||||||
Dir.glob('workers/*.rb').each {|w| require File.join(DIR_ROOT, "/#{w}") }
|
|
||||||
|
|
||||||
if defined?(Pry)
|
if defined?(Pry)
|
||||||
Pry.commands.alias_command 'c', 'continue'
|
Pry.commands.alias_command 'c', 'continue'
|
||||||
Pry.commands.alias_command 's', 'step'
|
Pry.commands.alias_command 's', 'step'
|
||||||
|
@ -60,9 +57,9 @@ if $config['pubsub_url'].nil? && ENV['RACK_ENV'] == 'production'
|
||||||
raise 'pubsub_url is missing from config'
|
raise 'pubsub_url is missing from config'
|
||||||
end
|
end
|
||||||
|
|
||||||
require File.join(DIR_ROOT, 'workers', 'thumbnail_worker.rb')
|
#require File.join(DIR_ROOT, 'workers', 'thumbnail_worker.rb')
|
||||||
require File.join(DIR_ROOT, 'workers', 'screenshot_worker.rb')
|
#require File.join(DIR_ROOT, 'workers', 'screenshot_worker.rb')
|
||||||
require File.join(DIR_ROOT, 'workers', 'email_worker.rb')
|
#require File.join(DIR_ROOT, 'workers', 'email_worker.rb')
|
||||||
|
|
||||||
Sequel.datetime_class = Time
|
Sequel.datetime_class = Time
|
||||||
Sequel.extension :core_extensions
|
Sequel.extension :core_extensions
|
||||||
|
@ -77,6 +74,9 @@ Sequel::Migrator.apply DB, './migrations'
|
||||||
Stripe.api_key = $config['stripe_api_key']
|
Stripe.api_key = $config['stripe_api_key']
|
||||||
|
|
||||||
Dir.glob('models/*.rb').each {|m| require File.join(DIR_ROOT, "#{m}") }
|
Dir.glob('models/*.rb').each {|m| require File.join(DIR_ROOT, "#{m}") }
|
||||||
|
Dir.glob('workers/*.rb').each {|w| require File.join(DIR_ROOT, "/#{w}") }
|
||||||
|
|
||||||
|
|
||||||
#DB.loggers << Logger.new(STDOUT) if ENV['RACK_ENV'] == 'development'
|
#DB.loggers << Logger.new(STDOUT) if ENV['RACK_ENV'] == 'development'
|
||||||
|
|
||||||
if ENV['RACK_ENV'] == 'development'
|
if ENV['RACK_ENV'] == 'development'
|
||||||
|
@ -100,3 +100,16 @@ Mail.defaults do
|
||||||
end
|
end
|
||||||
|
|
||||||
Sinatra::Application.set :erb, escape_html: true
|
Sinatra::Application.set :erb, escape_html: true
|
||||||
|
|
||||||
|
require 'sass/plugin/rack'
|
||||||
|
Sinatra::Application.use Sass::Plugin::Rack
|
||||||
|
|
||||||
|
Sass::Plugin.options[:template_location] = './public/assets/css'
|
||||||
|
Sass::Plugin.options[:css_location] = './public/assets/css'
|
||||||
|
Sass::Plugin.options[:style] = :nested
|
||||||
|
|
||||||
|
if ENV['RACK_ENV'] == 'production'
|
||||||
|
Sass::Plugin.options[:style] = :compressed
|
||||||
|
Sass::Plugin.options[:never_update] = true
|
||||||
|
Sass::Plugin.options[:full_exception] = false
|
||||||
|
end
|
230
models/site.rb
230
models/site.rb
|
@ -1,6 +1,7 @@
|
||||||
require 'tilt'
|
require 'tilt'
|
||||||
require 'rss'
|
require 'rss'
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
class Site < Sequel::Model
|
class Site < Sequel::Model
|
||||||
include Sequel::ParanoidDelete
|
include Sequel::ParanoidDelete
|
||||||
|
@ -23,6 +24,7 @@ class Site < Sequel::Model
|
||||||
image/x-icon
|
image/x-icon
|
||||||
application/pdf
|
application/pdf
|
||||||
application/pgp-keys
|
application/pgp-keys
|
||||||
|
application/pgp
|
||||||
text/xml
|
text/xml
|
||||||
application/xml
|
application/xml
|
||||||
audio/midi
|
audio/midi
|
||||||
|
@ -49,8 +51,8 @@ class Site < Sequel::Model
|
||||||
SITE_FILES_ROOT = File.join PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites')
|
SITE_FILES_ROOT = File.join PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites')
|
||||||
SCREENSHOTS_ROOT = File.join(PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'site_screenshots_test' : 'site_screenshots'))
|
SCREENSHOTS_ROOT = File.join(PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'site_screenshots_test' : 'site_screenshots'))
|
||||||
THUMBNAILS_ROOT = File.join(PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'site_thumbnails_test' : 'site_thumbnails'))
|
THUMBNAILS_ROOT = File.join(PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'site_thumbnails_test' : 'site_thumbnails'))
|
||||||
SCREENSHOTS_URL_ROOT = '/site_screenshots'
|
SCREENSHOTS_URL_ROOT = ENV['RACK_ENV'] == 'test' ? '/site_screenshots_test' : '/site_screenshots'
|
||||||
THUMBNAILS_URL_ROOT = '/site_thumbnails'
|
THUMBNAILS_URL_ROOT = ENV['RACK_ENV'] == 'test' ? '/site_thumbnails_test' : '/site_thumbnails'
|
||||||
IMAGE_REGEX = /jpg|jpeg|png|bmp|gif/
|
IMAGE_REGEX = /jpg|jpeg|png|bmp|gif/
|
||||||
LOSSLESS_IMAGE_REGEX = /png|bmp|gif/
|
LOSSLESS_IMAGE_REGEX = /png|bmp|gif/
|
||||||
LOSSY_IMAGE_REGEX = /jpg|jpeg/
|
LOSSY_IMAGE_REGEX = /jpg|jpeg/
|
||||||
|
@ -70,6 +72,8 @@ class Site < Sequel::Model
|
||||||
|
|
||||||
EMAIL_SANITY_REGEX = /.+@.+\..+/i
|
EMAIL_SANITY_REGEX = /.+@.+\..+/i
|
||||||
|
|
||||||
|
EDITABLE_FILE_EXT = /html|htm|txt|js|css|md/i
|
||||||
|
|
||||||
BANNED_TIME = 2592000 # 30 days in seconds
|
BANNED_TIME = 2592000 # 30 days in seconds
|
||||||
|
|
||||||
TITLE_MAX = 100
|
TITLE_MAX = 100
|
||||||
|
@ -197,16 +201,16 @@ class Site < Sequel::Model
|
||||||
FileUtils.mkdir_p files_path
|
FileUtils.mkdir_p files_path
|
||||||
|
|
||||||
%w{index not_found}.each do |name|
|
%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"
|
purge_cache "/#{name}.html"
|
||||||
ScreenshotWorker.perform_async values[:username], "#{name}.html"
|
ScreenshotWorker.perform_async values[:username], "#{name}.html"
|
||||||
end
|
end
|
||||||
|
|
||||||
FileUtils.cp template_file_path('cat.png'), file_path('cat.png')
|
FileUtils.cp template_file_path('cat.png'), files_path('cat.png')
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_file(filename)
|
def get_file(path)
|
||||||
File.read file_path(filename)
|
File.read files_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def before_destroy
|
def before_destroy
|
||||||
|
@ -252,8 +256,8 @@ class Site < Sequel::Model
|
||||||
FileUtils.mv files_path, File.join(PUBLIC_ROOT, 'banned_sites', username)
|
FileUtils.mv files_path, File.join(PUBLIC_ROOT, 'banned_sites', username)
|
||||||
}
|
}
|
||||||
|
|
||||||
site_files.file_list.collect {|f| f.filename}.each do |f|
|
file_list.each do |path|
|
||||||
purge_cache f
|
purge_cache path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -290,15 +294,6 @@ class Site < Sequel::Model
|
||||||
!@blockings.select {|b| b.site_id == site.id}.empty?
|
!@blockings.select {|b| b.site_id == site.id}.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.valid_filename?(filename)
|
|
||||||
return false if sanitize_filename(filename) != filename
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.sanitize_filename(filename)
|
|
||||||
filename.gsub(/[^a-zA-Z0-9_\-.]/, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.valid_username?(username)
|
def self.valid_username?(username)
|
||||||
!username.empty? && username.match(/^[a-zA-Z0-9_\-]+$/i)
|
!username.empty? && username.match(/^[a-zA-Z0-9_\-]+$/i)
|
||||||
end
|
end
|
||||||
|
@ -331,19 +326,24 @@ class Site < Sequel::Model
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def purge_cache(filename)
|
def purge_cache(path)
|
||||||
payload = {site: username, path: filename}
|
relative_path = path.gsub(base_files_path, '')
|
||||||
|
payload = {site: username, path: relative_path}
|
||||||
payload[:domain] = domain if !domain.empty?
|
payload[:domain] = domain if !domain.empty?
|
||||||
PurgeCacheWorker.perform_async payload
|
PurgeCacheWorker.perform_async payload
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_file(filename, uploaded)
|
def store_file(path, uploaded)
|
||||||
if File.exist?(file_path(filename)) &&
|
relative_path = scrubbed_path path
|
||||||
Digest::SHA2.file(file_path(filename)).digest == Digest::SHA2.file(uploaded.path).digest
|
path = files_path path
|
||||||
|
|
||||||
|
if File.exist?(path) &&
|
||||||
|
Digest::SHA2.file(path).digest == Digest::SHA2.file(uploaded.path).digest
|
||||||
return false
|
return false
|
||||||
end
|
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
|
new_title = Nokogiri::HTML(File.read(uploaded.path)).css('title').first.text
|
||||||
|
|
||||||
if new_title.length < TITLE_MAX
|
if new_title.length < TITLE_MAX
|
||||||
|
@ -352,20 +352,26 @@ class Site < Sequel::Model
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
FileUtils.mv uploaded.path, file_path(filename)
|
dirname = pathname.dirname.to_s
|
||||||
File.chmod(0640, file_path(filename))
|
|
||||||
|
|
||||||
purge_cache filename
|
if !File.exists? dirname
|
||||||
|
FileUtils.mkdir_p dirname
|
||||||
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
|
|
||||||
end
|
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], relative_path
|
||||||
|
elsif ext.match IMAGE_REGEX
|
||||||
|
ThumbnailWorker.perform_async values[:username], relative_path
|
||||||
|
end
|
||||||
|
|
||||||
|
SiteChange.record self, relative_path
|
||||||
|
|
||||||
if self.site_changed != true
|
if self.site_changed != true
|
||||||
self.site_changed = true
|
self.site_changed = true
|
||||||
|
@ -375,6 +381,20 @@ class Site < Sequel::Model
|
||||||
true
|
true
|
||||||
end
|
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
|
def increment_changed_count
|
||||||
self.changed_count += 1
|
self.changed_count += 1
|
||||||
self.updated_at = Time.now
|
self.updated_at = Time.now
|
||||||
|
@ -382,49 +402,61 @@ class Site < Sequel::Model
|
||||||
end
|
end
|
||||||
|
|
||||||
def files_zip
|
def files_zip
|
||||||
file_path = "/tmp/neocities-site-#{username}.zip"
|
zip_name = "neocities-#{username}"
|
||||||
|
|
||||||
Zip::File.open(file_path, Zip::File::CREATE) do |zipfile|
|
tmpfile = Tempfile.new 'neocities-site-zip'
|
||||||
file_list.collect {|f| f.filename}.each do |filename|
|
tmpfile.close
|
||||||
zipfile.add filename, file_path(filename)
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO Don't dump the zipfile into memory
|
tmpfile.path
|
||||||
zipfile = File.read file_path
|
|
||||||
File.delete file_path
|
|
||||||
zipfile
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_file(filename)
|
def delete_file(path)
|
||||||
begin
|
begin
|
||||||
FileUtils.rm file_path(filename)
|
FileUtils.rm files_path(path)
|
||||||
|
rescue Errno::EISDIR
|
||||||
|
FileUtils.remove_dir files_path(path), true
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
end
|
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
|
screenshots_delete(path) if ext.match HTML_REGEX
|
||||||
thumbnails_delete(filename) if ext.match IMAGE_REGEX
|
thumbnails_delete(path) if ext.match IMAGE_REGEX
|
||||||
|
|
||||||
SiteChangeFile.filter(site_id: self.id, filename: filename).delete
|
path = path[1..path.length] if path[0] == '/'
|
||||||
|
|
||||||
|
SiteChangeFile.filter(site_id: self.id, filename: path).delete
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def move_files_from(oldusername)
|
def move_files_from(oldusername)
|
||||||
FileUtils.mv files_path(oldusername), files_path
|
FileUtils.mv base_files_path(oldusername), base_files_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_new_html_file(name)
|
def install_new_html_file(path)
|
||||||
File.write file_path(name), render_template('index.erb')
|
File.write files_path(path), render_template('index.erb')
|
||||||
purge_cache name
|
purge_cache path
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_exists?(filename)
|
def file_exists?(path)
|
||||||
File.exist? file_path(filename)
|
File.exist? files_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_save
|
def after_save
|
||||||
|
@ -453,7 +485,7 @@ class Site < Sequel::Model
|
||||||
end
|
end
|
||||||
|
|
||||||
# def after_destroy
|
# def after_destroy
|
||||||
# FileUtils.rm_rf file_path
|
# FileUtils.rm_rf files_path
|
||||||
# super
|
# super
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
@ -568,16 +600,48 @@ class Site < Sequel::Model
|
||||||
File.join TEMPLATE_ROOT, name
|
File.join TEMPLATE_ROOT, name
|
||||||
end
|
end
|
||||||
|
|
||||||
def files_path(name=nil)
|
def base_files_path(name=username)
|
||||||
File.join SITE_FILES_ROOT, (name || username)
|
raise 'username missing' if name.nil? || name.empty?
|
||||||
|
File.join SITE_FILES_ROOT, name
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_path(filename)
|
# https://practicingruby.com/articles/implementing-an-http-file-server?u=dc2ab0f9bb
|
||||||
File.join files_path, filename
|
def scrubbed_path(path='')
|
||||||
|
path ||= ''
|
||||||
|
clean = []
|
||||||
|
|
||||||
|
parts = path.split '/'
|
||||||
|
|
||||||
|
parts.each do |part|
|
||||||
|
next if part.empty? || part == '.'
|
||||||
|
clean << part if part != '..'
|
||||||
|
end
|
||||||
|
|
||||||
|
clean.join '/'
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_list
|
def files_path(path='')
|
||||||
Dir.glob(File.join(files_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename}
|
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
|
end
|
||||||
|
|
||||||
def file_size_too_large?(size_in_bytes)
|
def file_size_too_large?(size_in_bytes)
|
||||||
|
@ -658,19 +722,19 @@ class Site < Sequel::Model
|
||||||
values[:hits].to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
|
values[:hits].to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
|
||||||
end
|
end
|
||||||
|
|
||||||
def screenshots_delete(filename)
|
def screenshots_delete(path)
|
||||||
SCREENSHOT_RESOLUTIONS.each do |res|
|
SCREENSHOT_RESOLUTIONS.each do |res|
|
||||||
begin
|
begin
|
||||||
FileUtils.rm screenshot_path(filename, res)
|
FileUtils.rm screenshot_path(path, res)
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnails_delete(filename)
|
def thumbnails_delete(path)
|
||||||
THUMBNAIL_RESOLUTIONS.each do |res|
|
THUMBNAIL_RESOLUTIONS.each do |res|
|
||||||
begin
|
begin
|
||||||
FileUtils.rm thumbnail_path(filename, res)
|
FileUtils.rm thumbnail_path(path, res)
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -680,34 +744,34 @@ class Site < Sequel::Model
|
||||||
Site.where(tags: tags).limit(limit, offset).order(:updated_at.desc).all
|
Site.where(tags: tags).limit(limit, offset).order(:updated_at.desc).all
|
||||||
end
|
end
|
||||||
|
|
||||||
def screenshot_path(filename, resolution)
|
def screenshot_path(path, resolution)
|
||||||
File.join(SCREENSHOTS_ROOT, values[:username], "#{filename}.#{resolution}.jpg")
|
File.join(SCREENSHOTS_ROOT, values[:username], "#{path}.#{resolution}.jpg")
|
||||||
end
|
end
|
||||||
|
|
||||||
def screenshot_exists?(filename, resolution)
|
def screenshot_exists?(path, resolution)
|
||||||
File.exist? File.join(SCREENSHOTS_ROOT, values[:username], "#{filename}.#{resolution}.jpg")
|
File.exist? File.join(SCREENSHOTS_ROOT, values[:username], "#{path}.#{resolution}.jpg")
|
||||||
end
|
end
|
||||||
|
|
||||||
def screenshot_url(filename, resolution)
|
def screenshot_url(path, resolution)
|
||||||
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.jpg"
|
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{path}.#{resolution}.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail_path(filename, resolution)
|
def thumbnail_path(path, resolution)
|
||||||
ext = File.extname(filename).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
ext = File.extname(path).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||||
File.join THUMBNAILS_ROOT, values[:username], "#{filename}.#{resolution}.#{ext}"
|
File.join THUMBNAILS_ROOT, values[:username], "#{path}.#{resolution}.#{ext}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail_exists?(filename, resolution)
|
def thumbnail_exists?(path, resolution)
|
||||||
File.exist? thumbnail_path(filename, resolution)
|
File.exist? thumbnail_path(path, resolution)
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail_delete(filename, resolution)
|
def thumbnail_delete(path, resolution)
|
||||||
File.rm thumbnail_path(filename, resolution)
|
File.rm thumbnail_path(path, resolution)
|
||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail_url(filename, resolution)
|
def thumbnail_url(path, resolution)
|
||||||
ext = File.extname(filename).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
ext = File.extname(path).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||||
"#{THUMBNAILS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.#{ext}"
|
"#{THUMBNAILS_URL_ROOT}/#{values[:username]}/#{path}.#{resolution}.#{ext}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_rss
|
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
|
|
1
public/assets/css/.gitignore
vendored
1
public/assets/css/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
# This file is here simplt to force git to allow the folder within the zip
|
|
|
@ -253,7 +253,7 @@
|
||||||
}
|
}
|
||||||
.file .title {
|
.file .title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 12px;
|
font-size: 8px;
|
||||||
color: #666;
|
color: #666;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
908
public/assets/css/neo.min.css
vendored
908
public/assets/css/neo.min.css
vendored
|
@ -1,908 +0,0 @@
|
||||||
@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,700,700italic,600,400italic,300italic,600italic);
|
|
||||||
@import url(//fonts.googleapis.com/css?family=Droid+Serif:400,700);
|
|
||||||
*, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
|
|
||||||
|
|
||||||
html { overflow-y: scroll; min-height: 100%; }
|
|
||||||
|
|
||||||
body { line-height: 1.5; }
|
|
||||||
|
|
||||||
body, blockquote, h1, h2, h3, h4, h5, h6, p, pre, form, fieldset, img, legend, table, th, td, caption, hr { border: 0; margin: 0; outline: 0; padding: 0; }
|
|
||||||
|
|
||||||
hr { border-top: 1px solid #ddd; display: block; height: 1px; margin: 24px 0; }
|
|
||||||
|
|
||||||
article, aside, details, figure, figcaption, footer, header, main, nav, section, summary { display: block; }
|
|
||||||
|
|
||||||
[hidden] { display: none; }
|
|
||||||
|
|
||||||
img { color: red; font-style: italic; }
|
|
||||||
|
|
||||||
audio, img, object, embed, video { max-width: 100%; }
|
|
||||||
|
|
||||||
audio, canvas, video { display: inline-block; }
|
|
||||||
|
|
||||||
audio:not([controls]) { display: none; height: 0; }
|
|
||||||
|
|
||||||
svg:not(:root) { overflow: hidden; }
|
|
||||||
|
|
||||||
small { display: block; }
|
|
||||||
|
|
||||||
p small, li small { display: inline; margin: 0; }
|
|
||||||
|
|
||||||
b, strong { font-weight: bold; }
|
|
||||||
|
|
||||||
i, em, dfn { font-style: italic; }
|
|
||||||
|
|
||||||
blockquote { font-size: 1.125em; font-style: italic; }
|
|
||||||
blockquote:before, blockquote:after { font-size: 1.375em; font-weight: 400; line-height: 1; position: relative; top: 2px; }
|
|
||||||
blockquote:before { content: '"'; left: -1px; }
|
|
||||||
blockquote:after { content: '"'; right: -1px; }
|
|
||||||
|
|
||||||
q { quotes: "\201C" "\201D" "\2018" "\2019"; }
|
|
||||||
|
|
||||||
abbr[title], dfn[title] { border-bottom: 1px dotted #cccccc; cursor: help; }
|
|
||||||
|
|
||||||
mark { background: yellow; color: #131313; }
|
|
||||||
|
|
||||||
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
|
|
||||||
|
|
||||||
sub { bottom: -.25em; }
|
|
||||||
|
|
||||||
sup { top: -.5em; }
|
|
||||||
|
|
||||||
code, kbd, pre, samp { background: #16414c; border-left: 6px solid #acd473; color: #a9b9b9; display: block; font-family: monospace, serif; font-size: 0.9em; padding: 12px 8px 12px 20px; }
|
|
||||||
|
|
||||||
pre { overflow: auto; white-space: pre-wrap; }
|
|
||||||
pre mark { background: #eee; border-bottom: 1px solid #ddd; color: #333; }
|
|
||||||
|
|
||||||
p code, p kbd, p pre, p samp, li code, li kbd, li pre, li samp, pre code, pre kbd, pre pre, pre samp { display: inline-block; margin: 0; padding: 4px; }
|
|
||||||
|
|
||||||
.code-Value { color: #24b9af; }
|
|
||||||
|
|
||||||
.code-Tag { color: #8ab04c; }
|
|
||||||
|
|
||||||
ol, ul { margin: 0 0 8px; padding: 0 0 0 24px; }
|
|
||||||
ol > li:last-child, ul > li:last-child { margin-bottom: 0; }
|
|
||||||
ol li ol, ol li ul, ul li ol, ul li ul { margin-top: 8px; }
|
|
||||||
ol li li, ul li li { font-size: 1em; }
|
|
||||||
|
|
||||||
dd { margin: 0 0 8px; padding-left: 16px; }
|
|
||||||
|
|
||||||
button, input, select, textarea { border: 0; font-family: inherit; font-size: 100%; line-height: normal; margin: 0; text-transform: none; }
|
|
||||||
|
|
||||||
button, html input[type='button'], input[type='reset'], input[type='submit'] { cursor: pointer; -webkit-appearance: button; }
|
|
||||||
|
|
||||||
input[type='search'] { -webkit-appearance: textfield; }
|
|
||||||
|
|
||||||
input[disabled] { background: #eeeeee; cursor: not-allowed; }
|
|
||||||
|
|
||||||
input[readonly] { background: #fafafa; }
|
|
||||||
|
|
||||||
input[type='search']::-webkit-search-decoration { -webkit-appearance: none; }
|
|
||||||
|
|
||||||
buton::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
|
|
||||||
|
|
||||||
textarea { overflow: auto; vertical-align: top; border: 2px solid #DCE4EC; }
|
|
||||||
|
|
||||||
.tooltip-inner { white-space: pre-wrap; }
|
|
||||||
|
|
||||||
table { border-collapse: collapse; border-spacing: 0; }
|
|
||||||
|
|
||||||
.table-Base, .table-Border, .table-Stripe { background: #fafafa; font-size: 0.9em; width: 100%; }
|
|
||||||
.table-Base th, .table-Border th, .table-Stripe th, .table-Base td, .table-Border td, .table-Stripe td { padding: 4px; text-align: left; }
|
|
||||||
|
|
||||||
.table-Header { background: #eee; }
|
|
||||||
|
|
||||||
.table-Footer { background: #e3e3e3; }
|
|
||||||
|
|
||||||
.table-Border { border-bottom: 1px solid #cccccc; border-right: 1px solid #cccccc; }
|
|
||||||
.table-Border tr { border-top: 1px solid #cccccc; }
|
|
||||||
.table-Border th, .table-Border td { border-left: 1px solid #cccccc; }
|
|
||||||
|
|
||||||
.table-Stripe tr:nth-child(2n) { background: #eeeeee; }
|
|
||||||
|
|
||||||
.row, .c-Row { margin-left: -20px; margin-bottom: 20px; }
|
|
||||||
|
|
||||||
.col { float: left; margin-bottom: 0 !important; padding-left: 20px; position: relative; width: 100%; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .col { float: none; padding: 0; } }
|
|
||||||
|
|
||||||
.c-Row { font-size: 0; text-align: center; }
|
|
||||||
.c-Row .col { display: inline-block; float: none; font-size: 16px; text-align: left; }
|
|
||||||
|
|
||||||
.col-90 { width: 90%; }
|
|
||||||
|
|
||||||
.col-80 { width: 80%; }
|
|
||||||
|
|
||||||
.col-75 { width: 75%; }
|
|
||||||
|
|
||||||
.col-66 { width: 66.6666%; }
|
|
||||||
|
|
||||||
.col-60 { width: 60%; }
|
|
||||||
|
|
||||||
.col-50 { width: 50%; }
|
|
||||||
|
|
||||||
.col-40 { width: 40%; }
|
|
||||||
|
|
||||||
.col-33 { width: 33.3333%; }
|
|
||||||
|
|
||||||
.col-25, .website-Gallery li { width: 25%; }
|
|
||||||
|
|
||||||
.col-20 { width: 20%; }
|
|
||||||
|
|
||||||
.col-10 { width: 10%; }
|
|
||||||
|
|
||||||
.rfl { padding-left: 300px; }
|
|
||||||
.rfl .f-Col { float: left; margin-left: -280px; width: 280px; }
|
|
||||||
|
|
||||||
.rfr { padding-right: 300px; }
|
|
||||||
.rfr .f-Col { float: right; margin-right: -300px; width: 280px; }
|
|
||||||
|
|
||||||
.block { background: #ccc; color: #333; padding: 4px; }
|
|
||||||
.block > :last-child { margin-bottom: 0; }
|
|
||||||
|
|
||||||
nav ul, nav ol { list-style: none; margin: 0; padding: 0; }
|
|
||||||
nav li { margin: 0; }
|
|
||||||
nav a { display: inline-block; padding: 4px 8px; text-decoration: underline; }
|
|
||||||
nav a:hover { text-decoration: none; }
|
|
||||||
|
|
||||||
.grouping { padding: 4px 0; }
|
|
||||||
|
|
||||||
.fs-Legend { border: 1px solid #cccccc; margin-bottom: 8px; padding: 8px 12px; }
|
|
||||||
|
|
||||||
legend, .legend { font-size: 1.375em; margin-left: -4px; padding: 0 4px; }
|
|
||||||
|
|
||||||
/* Text Input Areas & Labels */
|
|
||||||
.text-Label, .option-Container { font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; cursor: pointer; display: block; margin-bottom: 4px; }
|
|
||||||
|
|
||||||
.dis-Label { cursor: not-allowed; }
|
|
||||||
|
|
||||||
.input-Area, .text-Area, .select-Container, .input-Number { background: #fff; border: 2px solid #cccccc; font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; line-height: 1.25; margin-bottom: 8px; padding: 8px 4px; width: 80%; }
|
|
||||||
.input-Area:focus, .text-Area:focus, .select-Container:focus, .input-Number:focus { background: #f8f8f8; border: 2px solid #50B6D5; -moz-box-shadow: 0 0 6px rgba(0, 0, 0, 0.25) inset; -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.25) inset; box-shadow: 0 0 6px rgba(0, 0, 0, 0.25) inset; }
|
|
||||||
|
|
||||||
.text-Area { display: block; min-height: 150px; resize: vertical; width: 100%; }
|
|
||||||
|
|
||||||
/* Check/Radio Inputs & Labels */
|
|
||||||
.option-Container { position: relative; }
|
|
||||||
.option-Container:hover .btn-Radio, .option-Container:hover .btn-Check { border-color: #333; }
|
|
||||||
|
|
||||||
.option-Label { cursor: pointer; display: block; padding-left: 28px; position: relative; z-index: 2; }
|
|
||||||
|
|
||||||
.input-Hide { border: 0 !important; height: 1px !important; left: -999999px !important; overflow: hidden !important; position: absolute !important; width: 1px !important; }
|
|
||||||
|
|
||||||
.btn-Radio, .btn-Check { background: #eeeeee; border: 1px solid #ccc; font-size: 14px; font-weight: bold; height: 19px; left: 0; padding: 2px; position: absolute; top: 0; width: 19px; }
|
|
||||||
.btn-Radio:hover, .input-Radio.selected-Radio .btn-Radio, .btn-Check:hover, .input-Check.selected-Check .btn-Check { background: #e93250; }
|
|
||||||
|
|
||||||
.btn-Radio { -moz-border-radius: 15px; -webkit-border-radius: 15px; border-radius: 15px; background-clip: padding-box; }
|
|
||||||
|
|
||||||
.input-Radio.selected-Radio .btn-Radio { border-color: #333; }
|
|
||||||
|
|
||||||
.input-Check.selected-Check .btn-Check { border-color: #333; }
|
|
||||||
|
|
||||||
.ifChecked { visibility: hidden; }
|
|
||||||
|
|
||||||
.selected-Check .ifChecked { visibility: visible; }
|
|
||||||
|
|
||||||
/* Drop Down Selection Inputs */
|
|
||||||
.select-Container { background: url(../img/drop-Arrow.png) no-repeat 99% center white; display: inline-block; overflow: hidden; }
|
|
||||||
|
|
||||||
.input-Select { background: none; border: 0; font-size: 0.9em; padding-right: 16px; width: 120%; -webkit-appearance: textarea; }
|
|
||||||
|
|
||||||
.file-Input-Area { position: relative; }
|
|
||||||
.file-Input-Area label { cursor: default; }
|
|
||||||
|
|
||||||
.input-File { cursor: pointer; left: 0; height: 100%; opacity: 0; position: absolute; top: 0; width: 100%; z-index: 9; }
|
|
||||||
|
|
||||||
.input-File-Text { cursor: pointer; display: inline-block; }
|
|
||||||
|
|
||||||
/* Inputs not supported in all browsers */
|
|
||||||
.input-Color { border: 1px solid #cccccc; font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; margin-bottom: 8px; padding: 0 4px; height: 45px; width: 50%; }
|
|
||||||
.input-Color:focus { background: #f8f8f8; border: 1px solid #50B6D5; -moz-box-shadow: 0 0 6px rgba(0, 0, 0, 0.25) inset; -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.25) inset; box-shadow: 0 0 6px rgba(0, 0, 0, 0.25) inset; }
|
|
||||||
|
|
||||||
.input-Number { font-size: 0.9em; }
|
|
||||||
|
|
||||||
::-webkit-input-placeholder { /* WebKit browsers */ color: #5e7f8d; font-style: italic; }
|
|
||||||
|
|
||||||
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ color: #5e7f8d; font-style: italic; }
|
|
||||||
|
|
||||||
::-moz-placeholder { /* Mozilla Firefox 19+ */ color: #5e7f8d; font-style: italic; }
|
|
||||||
|
|
||||||
:-ms-input-placeholder { /* Internet Explorer 10+ */ color: #5e7f8d; font-style: italic; }
|
|
||||||
|
|
||||||
.btn, .btn-Radio, .btn-Check, .btn-Small, .btn-Large, .btn-XLarge, .btn-Wide, .btn-Action, .btn-Action-2, .btn-Action-3, .btn-Neg, .btn-Neg:hover, .btn-Disable, .btn-Disable:hover, .btn-Disable:visited, .btn-Square, .btn-Round { background: #343434; -moz-border-radius: 18px; -webkit-border-radius: 18px; border-radius: 18px; background-clip: padding-box; -moz-box-shadow: 0 0 6px rgba(0, 0, 0, 0.25); -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.25); box-shadow: 0 0 6px rgba(0, 0, 0, 0.25); color: #f8f8f8; cursor: pointer; display: inline-block; font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-size: 0.9em; line-height: 1; padding: 8px 20px; text-align: center; text-decoration: none !important; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); -moz-transition: all 0.25s ease-in-out 0s; -ms-transition: all 0.25s ease-in-out 0s; -o-transition: all 0.25s ease-in-out 0s; -webkit-transition: all 0.25s ease-in-out 0s; transition: all 0.25s ease-in-out 0s; vertical-align: middle; -webkit-appearance: none; }
|
|
||||||
.btn.btn-Pad, .btn-Pad.btn-Radio, .btn-Pad.btn-Check, .btn-Pad.btn-Small, .btn-Pad.btn-Large, .btn-Pad.btn-XLarge, .btn-Pad.btn-Wide, .btn-Pad.btn-Action, .btn-Pad.btn-Action-2, .btn-Pad.btn-Action-3, .btn-Pad.btn-Neg, .btn-Pad.btn-Disable, .btn-Pad.btn-Square, .btn-Pad.btn-Round { padding: 8px 16px; }
|
|
||||||
.btn:hover, .btn:active, .btn-Radio:hover, .input-Radio.selected-Radio .btn-Radio, .btn-Check:hover, .input-Check.selected-Check .btn-Check, .btn-Small:hover, .btn-Large:hover, .btn-XLarge:hover, .btn-Wide:hover, .btn-Action:hover, .btn-Action-2:hover, .btn-Action-3:hover, .btn-Neg:hover, .btn-Disable:hover, .btn-Square:hover, .btn-Round:hover, .btn-Radio:active, .btn-Check:active, .btn-Small:active, .btn-Large:active, .btn-XLarge:active, .btn-Wide:active, .btn-Action:active, .btn-Action-2:active, .btn-Action-3:active, .btn-Neg:active, .btn-Disable:active, .btn-Square:active, .btn-Round:active { background: #131313; color: #f8f8f8; }
|
|
||||||
.btn:visited, .btn-Radio:visited, .btn-Check:visited, .btn-Small:visited, .btn-Large:visited, .btn-XLarge:visited, .btn-Wide:visited, .btn-Action:visited, .btn-Action-2:visited, .btn-Action-3:visited, .btn-Neg:visited, .btn-Disable:visited, .btn-Square:visited, .btn-Round:visited { color: #f8f8f8; }
|
|
||||||
|
|
||||||
.btn-Small { font-size: 0.875em; padding: 4px 8px; }
|
|
||||||
.btn-Small.btn-Pad { padding: 4px 12px; }
|
|
||||||
|
|
||||||
.btn-Large { font-size: 2em; padding: 12px; }
|
|
||||||
.btn-Large.btn-Pad { padding: 12px 20px; }
|
|
||||||
|
|
||||||
.btn-XLarge { font-size: 2.5em; padding: 12px 16px; }
|
|
||||||
.btn-XLarge.btn-Pad { padding: 16px 24px; }
|
|
||||||
|
|
||||||
.btn-Wide { display: block; }
|
|
||||||
|
|
||||||
.btn-Action { background: #e93250; background: -webkit-linear-gradient(top, #e93250, #b11f36); background: -moz-linear-gradient(top, #e93250, #b11f36); background: -o-linear-gradient(top, #e93250, #b11f36); background: linear-gradient(top, #e93250, #b11f36); }
|
|
||||||
.btn-Action:hover { background: -webkit-linear-gradient(top, #d51c3a, #841526); background: -moz-linear-gradient(top, #d51c3a, #841526); background: -o-linear-gradient(top, #d51c3a, #841526); background: linear-gradient(top, #d51c3a, #841526); }
|
|
||||||
|
|
||||||
.btn-Action-2 { background: #daeea5; }
|
|
||||||
.btn-Action-2:hover { background: #c0e265; }
|
|
||||||
|
|
||||||
.btn-Action-3 { background: #f6f0e6; }
|
|
||||||
.btn-Action-3:hover { background: #e2ceae; }
|
|
||||||
|
|
||||||
.btn-Neg, .btn-Neg:hover { background: #aaaaaa; color: #f8f8f8; }
|
|
||||||
|
|
||||||
.btn-Disable, .btn-Disable:hover, .btn-Disable:visited { background: #fafafa; border: 1px solid #eee; color: #343434; cursor: default; }
|
|
||||||
|
|
||||||
.btn-Square { -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; background-clip: padding-box; }
|
|
||||||
|
|
||||||
.btn-Round { -moz-border-radius: 12px; -webkit-border-radius: 12px; border-radius: 12px; background-clip: padding-box; }
|
|
||||||
|
|
||||||
.nav ul, .nav ol { border: 1px solid #ccc; padding: 4px; }
|
|
||||||
.nav li { margin: 0; }
|
|
||||||
.nav a { display: block; padding: 4px 8px; text-decoration: underline; }
|
|
||||||
.nav a:hover { text-decoration: none; }
|
|
||||||
|
|
||||||
.h-Nav > li, .bread > li { float: left; }
|
|
||||||
|
|
||||||
.c-Nav { font-size: 0; text-align: center; }
|
|
||||||
|
|
||||||
.c-Nav > li, .ah-Nav a { display: inline-block; font-size: 16px; }
|
|
||||||
|
|
||||||
.v-Nav { /* really only needs styling applied to it, as vertical nav is default */ }
|
|
||||||
|
|
||||||
.ah-Nav { font-size: 0; }
|
|
||||||
|
|
||||||
.av-Nav .drop-Start { display: block; }
|
|
||||||
|
|
||||||
.drop-Start { position: relative; }
|
|
||||||
.drop-Start:hover { background: #eee; }
|
|
||||||
.drop-Start:hover .drop-Menu { visibility: visible; }
|
|
||||||
.drop-Start a { white-space: nowrap; }
|
|
||||||
|
|
||||||
.drop-Menu { background: #eee; border: 1px solid #ddd; position: absolute; visibility: hidden; z-index: 5; }
|
|
||||||
|
|
||||||
.v-Nav .drop-Menu, .av-Nav .drop-Menu { left: 100%; top: 0; }
|
|
||||||
|
|
||||||
.h-Nav .drop-Menu, .bread .drop-Menu, .ah-Nav .drop-Menu { left: 0; top: 100%; }
|
|
||||||
|
|
||||||
.bread li:last-child:after { content: none; }
|
|
||||||
.bread li:after { content: ">"; display: inline-block; }
|
|
||||||
.bread a { display: inline-block; }
|
|
||||||
.bread span { display: inline-block; padding: 4px 8px; }
|
|
||||||
|
|
||||||
.float-Left { float: left; }
|
|
||||||
|
|
||||||
.float-Right { float: right; }
|
|
||||||
|
|
||||||
.float-None { float: none; }
|
|
||||||
|
|
||||||
.clear { clear: both; }
|
|
||||||
|
|
||||||
.clearfix, .row, .c-Row, nav ul, nav ol, .media, .media-Reverse, .media-No-Wrap-Reverse, .media-No-Wrap { *zoom: 1; }
|
|
||||||
.clearfix:before, .row:before, .c-Row:before, nav ul:before, nav ol:before, .media:before, .media-Reverse:before, .media-No-Wrap-Reverse:before, .media-No-Wrap:before, .clearfix:after, .row:after, .c-Row:after, nav ul:after, nav ol:after, .media:after, .media-Reverse:after, .media-No-Wrap-Reverse:after, .media-No-Wrap:after { content: ""; display: table; }
|
|
||||||
.clearfix:after, .row:after, .c-Row:after, nav ul:after, nav ol:after, .media:after, .media-Reverse:after, .media-No-Wrap-Reverse:after, .media-No-Wrap:after { clear: both; }
|
|
||||||
|
|
||||||
.overflow { overflow: hidden; }
|
|
||||||
|
|
||||||
blockquote, h1, h2, h3, h4, h5, h6, li, p, small, code, kbd, pre, samp, dt, form, table { margin-bottom: 12px; }
|
|
||||||
|
|
||||||
.d-Block, .show { display: block; }
|
|
||||||
|
|
||||||
.d-None, .mobile-Show { display: none; }
|
|
||||||
|
|
||||||
.hidden { display: none !important; visibility: hidden; }
|
|
||||||
|
|
||||||
.invis { border: 0 !important; height: 1px !important; left: -999999px !important; overflow: hidden !important; position: absolute !important; width: 1px !important; }
|
|
||||||
|
|
||||||
.pic, .pic-Rounded { border: 1px solid white; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-clip: padding-box; -moz-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); display: inline-block; overflow: hidden; width: 90px; }
|
|
||||||
.pic img, .pic-Rounded img { display: block; }
|
|
||||||
|
|
||||||
.pic-Rounded { -moz-border-radius: 100px; -webkit-border-radius: 100px; border-radius: 100px; background-clip: padding-box; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
Code Example
|
|
||||||
--------------------------
|
|
||||||
<a href="#" title="TITLE" class="pic-Rounded">
|
|
||||||
<img src="assets/img/little-V.jpg" alt="" />
|
|
||||||
</a>
|
|
||||||
*/
|
|
||||||
.media, .media-Reverse, .media-No-Wrap-Reverse, .media-No-Wrap { margin-bottom: 16px; position: relative; z-index: 2; }
|
|
||||||
.media .media-Text > :last-child, .media-Reverse .media-Text > :last-child, .media-No-Wrap-Reverse .media-Text > :last-child, .media-No-Wrap .media-Text > :last-child { margin-bottom: 0; }
|
|
||||||
.media .media-Object, .media-Reverse .media-Object, .media-No-Wrap-Reverse .media-Object, .media-No-Wrap .media-Object { float: left; margin: 0 16px 8px 0; max-width: 30%; }
|
|
||||||
|
|
||||||
.media-Reverse .media-Object, .media-No-Wrap-Reverse .media-Object { float: right; margin: 0 0 8px 16px; max-width: 30%; }
|
|
||||||
|
|
||||||
.media-No-Wrap { padding-left: 100px; }
|
|
||||||
.media-No-Wrap .media-Text { float: left; width: 100%; }
|
|
||||||
.media-No-Wrap .media-Object { margin-left: -100px; margin-right: 0; }
|
|
||||||
|
|
||||||
.media-No-Wrap-Reverse { padding-right: 100px; }
|
|
||||||
.media-No-Wrap-Reverse .media-Object { margin-right: -100px; margin-left: 0; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
Code Example
|
|
||||||
--------------------------
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-Object">
|
|
||||||
<img src="assets/img/little-V.jpg" alt="" />
|
|
||||||
</div>
|
|
||||||
<div class="media-Text">
|
|
||||||
<h1>Title for Meida Element</h1>
|
|
||||||
<p>Paragraph text to go along with media element.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
-- Divs were used in this example, but the media element could be applied to various situations.
|
|
||||||
Ie. could be used for a header or footer area where a logo is placed on the right or left w/text to the side.
|
|
||||||
*/
|
|
||||||
.emph-Block { background: #fafafa; border: 1px solid #ddd; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-clip: padding-box; -moz-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); margin-bottom: 16px; min-height: 50px; padding: 16px; }
|
|
||||||
.emph-Block > :last-child { margin-bottom: 0; }
|
|
||||||
|
|
||||||
.kill-List, .intro-List { list-style: none; padding: 0; }
|
|
||||||
|
|
||||||
.numbered { list-style-type: decimal; }
|
|
||||||
|
|
||||||
.shout-Out { background: #f8f8f8; border-left: 4px solid #343434; padding: 16px; }
|
|
||||||
.shout-Out:before, .shout-Out:after { display: none; }
|
|
||||||
|
|
||||||
.action-Link:after { content: "\00A0" "\00BB"; }
|
|
||||||
|
|
||||||
.txt-Center { text-align: center; }
|
|
||||||
|
|
||||||
.txt-Left { text-align: left; }
|
|
||||||
|
|
||||||
.txt-Right { text-align: right; }
|
|
||||||
|
|
||||||
.txt-Just { text-align: justify; }
|
|
||||||
|
|
||||||
.highlight, .slant { background: yellow; color: #131313; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; background-clip: padding-box; display: inline-block; padding: 4px; vertical-align: text-top; }
|
|
||||||
|
|
||||||
.slant { -moz-transform: skewX(-16deg); -ms-transform: skewX(-16deg); -o-transform: skewX(-16deg); -webkit-transform: skewX(-16deg); transform: skewX(-16deg); }
|
|
||||||
|
|
||||||
.text-Correct { display: inline-block; -moz-transform: skewX(16deg); -ms-transform: skewX(16deg); -o-transform: skewX(16deg); -webkit-transform: skewX(16deg); transform: skewX(16deg); }
|
|
||||||
|
|
||||||
/*
|
|
||||||
Code Example: Slanted Text w/Highlight
|
|
||||||
--------------------------
|
|
||||||
<span class="slant">
|
|
||||||
Text Goes here
|
|
||||||
</span>
|
|
||||||
|
|
||||||
Code Example: Normal flowing text w/slanted highlight
|
|
||||||
--------------------------
|
|
||||||
<span class="slant">
|
|
||||||
<span class="text-Correct">Text Goes here</span>
|
|
||||||
</span>
|
|
||||||
*/
|
|
||||||
.multi { -moz-column-count: 3; -webkit-column-count: 3; column-count: 3; -moz-column-gap: 40px; -webkit-column-gap: 40px; column-gap: 40px; -moz-column-rule: 1px outset #eeeeee; -webkit-column-rule: 1px outset #eeeeee; column-rule: 1px outset #eeeeee; }
|
|
||||||
|
|
||||||
body { font-family: "Lucida Grande", verdana, "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; }
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 { font-family: "Droid Serif", Georgia, "Times New Roman", Times, serif; }
|
|
||||||
|
|
||||||
.giga { font-size: 4.375em; font-weight: 700; }
|
|
||||||
|
|
||||||
.ultra { font-size: 3.75em; font-weight: 600; }
|
|
||||||
|
|
||||||
.mega { font-size: 3.5em; font-weight: 300; }
|
|
||||||
|
|
||||||
h1, .alpha { font-size: 3.125em; font-weight: 400; }
|
|
||||||
|
|
||||||
h2, .beta { font-size: 2.5em; font-weight: 400; }
|
|
||||||
|
|
||||||
h3, .gamma { font-size: 2em; font-weight: 400; }
|
|
||||||
|
|
||||||
h4, .delta { font-size: 1.625em; font-weight: 300; }
|
|
||||||
|
|
||||||
h5, .eps { font-size: 1.375em; font-weight: 300; }
|
|
||||||
|
|
||||||
h6, .zeta { font-size: 1.125em; font-weight: 300; }
|
|
||||||
|
|
||||||
p, li, .base { font-size: 0.9em; font-weight: 300; }
|
|
||||||
|
|
||||||
small, .tiny { font-size: 0.875em; font-weight: 300; }
|
|
||||||
|
|
||||||
.mini { font-size: 0.75em; font-weight: 300; }
|
|
||||||
|
|
||||||
.action-Link { float: right; }
|
|
||||||
|
|
||||||
body { background: #CCDF9B; }
|
|
||||||
|
|
||||||
::-moz-selection { background: #e93250; color: #eeeeee; text-shadow: none; }
|
|
||||||
|
|
||||||
::selection { background: #e93250; color: #eeeeee; text-shadow: none; }
|
|
||||||
|
|
||||||
.page { min-height: 600px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .page { min-height: 25px; } }
|
|
||||||
|
|
||||||
.content, .footer-Content { margin: 0 auto; max-width: 1200px; padding: 20px; }
|
|
||||||
.content > :last-child, .footer-Content > :last-child { margin-bottom: 0; }
|
|
||||||
|
|
||||||
a { color: #e93250; }
|
|
||||||
a:hover, a:active { color: #ba142f; }
|
|
||||||
a:visited { color: #A5424B; }
|
|
||||||
|
|
||||||
:focus, a:focus, a:active, input[type="submit"]::-moz-focus-inner { outline: none; }
|
|
||||||
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .col-33, .col-40, .col-60 { float: none; width: 100%; } }
|
|
||||||
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .col-50 { float: none; padding: 0; width: 100% !important; } }
|
|
||||||
|
|
||||||
.header-Base { background: #65a0ad; border-bottom: 6px solid #e93250; min-height: 42px; overflow: hidden; }
|
|
||||||
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .header-Content { padding: 0; } }
|
|
||||||
|
|
||||||
.blurb { background: #fff; }
|
|
||||||
|
|
||||||
.header-Intro { background: url(../img/neocity.jpg) 95% bottom no-repeat; min-height: 214px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .header-Intro { -moz-background-size: cover; -ms-background-size: cover; -o-background-size: cover; -webkit-background-size: cover; background-size: cover; min-height: 2px; } }
|
|
||||||
|
|
||||||
.header-Outro { background: #30424b -moz-linear-gradient(top, #2b3c43 0%, #354751 100%); /* FF3.6+ */ background: #30424b -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b3c43), color-stop(100%, #354751)); /* Chrome,Safari4+ */ background: #30424b -webkit-linear-gradient(top, #2b3c43 0%, #354751 100%); /* Chrome10+,Safari5.1+ */ background: #30424b -o-linear-gradient(top, #2b3c43 0%, #354751 100%); /* Opera 11.10+ */ background: #30424b -ms-linear-gradient(top, #2b3c43 0%, #354751 100%); /* IE10+ */ background: #30424b linear-gradient(to bottom, #2b3c43 0%, #354751 100%); /* W3C */ -moz-box-shadow: inset 0 7px 10px 0 rgba(0, 0, 0, 0.1); -webkit-box-shadow: inset 0 7px 10px 0 rgba(0, 0, 0, 0.1); box-shadow: inset 0 7px 10px 0 rgba(0, 0, 0, 0.1); color: #fafafa; }
|
|
||||||
|
|
||||||
.hp .header-Outro .col-50 { width: 48%; }
|
|
||||||
|
|
||||||
.hp .header-Outro .signup-Area { float: right; }
|
|
||||||
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .intro-List { margin-bottom: 20px; padding: 20px !important; } }
|
|
||||||
.intro-List li { padding-left: 36px; padding-right: 12px; margin-bottom: 20px; }
|
|
||||||
.intro-List h2 { margin-bottom: 2px; }
|
|
||||||
.intro-List p { color: #B2BCC1; }
|
|
||||||
|
|
||||||
.intro-Icon { background: url(../img/icons.png) no-repeat; display: block; height: 37px; left: -10px; position: absolute; top: 2px; width: 37px; }
|
|
||||||
|
|
||||||
.intro-Tools { position: relative; }
|
|
||||||
|
|
||||||
.intro-Question { position: relative; }
|
|
||||||
.intro-Question .intro-Icon { background-position: 0 -40px; }
|
|
||||||
|
|
||||||
.intro-Social { position: relative; }
|
|
||||||
.intro-Social .intro-Icon { background-position: 0 -80px; }
|
|
||||||
|
|
||||||
.signup-Area { min-height: 100px; position: relative; }
|
|
||||||
|
|
||||||
.signup-Form { background: #354751; border-radius: 4px 4px 0 0; -moz-box-shadow: 1px 2px 12px 2px rgba(0, 0, 0, 0.15); -webkit-box-shadow: 1px 2px 12px 2px rgba(0, 0, 0, 0.15); box-shadow: 1px 2px 12px 2px rgba(0, 0, 0, 0.15); height: 600%; overflow: hidden; position: absolute; top: -45px; width: 95%; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .signup-Form { height: auto; margin: 0; overflow: visible; padding-bottom: 20px; position: static; width: auto; } }
|
|
||||||
.signup-Form h2 { margin-bottom: 0; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.5); font-size: 1.8em; }
|
|
||||||
.signup-Form hr { border-bottom: 1px solid #4a6677; border-top: 1px solid #1d282d; margin: 4px 0 22px; }
|
|
||||||
.signup-Form fieldset { background: url(../img/sign-up-bg.png) repeat-x center top; padding: 20px 33px; }
|
|
||||||
.signup-Form label { color: #81b8c6; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .signup-Form label { font-size: 70%; } }
|
|
||||||
.signup-Form .input-Area { background: #29383f; border: 0 solid black; -moz-box-shadow: inset 1px 3px 10px 0px rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 1px 3px 10px 0px rgba(0, 0, 0, 0.2); box-shadow: inset 1px 3px 10px 0px rgba(0, 0, 0, 0.2); color: #557380; margin-bottom: 28px; margin-right: 4px; padding: 11px 12px 9px 12px; width: 62%; }
|
|
||||||
.signup-Form .input-Area:focus { color: #eee; }
|
|
||||||
.signup-Form .btn-Action { padding: 10px 25px; }
|
|
||||||
|
|
||||||
.small-Nav { background: #30424B; display: none; position: fixed; right: 0; top: 0; width: 50px; z-index: 9999; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .small-Nav { display: block; } }
|
|
||||||
|
|
||||||
.header-Nav { background: #5e95a1; border-bottom: 1px solid #92B4BD; -moz-transition: all 0.35s; -ms-transition: all 0.35s; -o-transition: all 0.35s; -webkit-transition: all 0.35s; transition: all 0.35s; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .header-Nav { position: fixed; top: -900px !important; } }
|
|
||||||
.header-Nav.show-Nav { top: 0 !important; }
|
|
||||||
.header-Nav a, .header-Nav a:visited { color: #fff; padding: 8px 12px; text-decoration: none; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .header-Nav a, .header-Nav a:visited { display: block; } }
|
|
||||||
.header-Nav a:hover, .header-Nav a:visited:hover { background: #528995; text-decoration: underline; }
|
|
||||||
.header-Nav a.selected, .header-Nav a:active, .header-Nav a:visited.selected, .header-Nav a:visited:active { background: #528995; text-decoration: underline; }
|
|
||||||
|
|
||||||
.constant-Nav { float: left; position: relative; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .constant-Nav { float: none; }
|
|
||||||
.constant-Nav li { float: none; } }
|
|
||||||
|
|
||||||
.status-Nav { float: right; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .status-Nav { float: none; } }
|
|
||||||
.status-Nav li { float: left; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .status-Nav li { float: none; } }
|
|
||||||
|
|
||||||
.hp .header-Base { padding-top: 46px; }
|
|
||||||
|
|
||||||
.hp .header-Nav { left: 0; position: fixed; top: 0; width: 100%; z-index: 3; }
|
|
||||||
|
|
||||||
.hp .hp-Logo { left: -90px; position: fixed; -moz-transition: all 0.35s; -ms-transition: all 0.35s; -o-transition: all 0.35s; -webkit-transition: all 0.35s; transition: all 0.35s; }
|
|
||||||
.hp .hp-Logo.in-View { left: 0 !important; z-index: 99; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .hp .hp-Logo.in-View { left: -90px !important; } }
|
|
||||||
|
|
||||||
.hp .logo { padding-top: 45px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .hp .logo { padding-left: 20px; padding-right: 20px; } }
|
|
||||||
|
|
||||||
.constant-Nav { margin-left: -88px; -moz-transition: all 0.35s; -ms-transition: all 0.35s; -o-transition: all 0.35s; -webkit-transition: all 0.35s; transition: all 0.35s; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .constant-Nav { margin-left: 0; } }
|
|
||||||
.constant-Nav.in-View { margin-left: 0; padding-left: 70px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .constant-Nav.in-View { padding-left: 0; } }
|
|
||||||
|
|
||||||
.add-Stripe { border-bottom: 6px solid #E93250; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .add-Stripe { border: 0; } }
|
|
||||||
|
|
||||||
.interior .page { padding-top: 22px; float: left; width: 100%; }
|
|
||||||
|
|
||||||
.interior .signup-Form { top: 0; }
|
|
||||||
|
|
||||||
.interior .header-Base { left: 0; overflow: visible; position: fixed; top: 0; width: 100%; z-index: 9; }
|
|
||||||
|
|
||||||
.int-Logo { left: 0; position: absolute; top: 0; width: 70px; z-index: 9; }
|
|
||||||
|
|
||||||
.interior .header-Nav { padding-left: 70px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .interior .header-Nav { width: 100%; } }
|
|
||||||
|
|
||||||
.interior .constant-Nav { margin: 0; }
|
|
||||||
|
|
||||||
.content-Base { background: #f6f0e6; min-height: 500px; padding-bottom: 50px; padding-top: 10px; }
|
|
||||||
.content-Base h1, .content-Base h2, .content-Base h3, .content-Base h4, .content-Base h5, .content-Base h6 { color: #e93250; }
|
|
||||||
|
|
||||||
.content, .footer-Content, .footer-Content { padding: 20px 3%; }
|
|
||||||
|
|
||||||
.single-Col { max-width: 800px; }
|
|
||||||
|
|
||||||
.twitter-tweet.twitter-tweet-rendered { margin: 0 auto 30px !important; }
|
|
||||||
|
|
||||||
.interior .header-Outro { padding-top: 30px; overflow: hidden; }
|
|
||||||
|
|
||||||
.interior .header-Outro h1 { font-size: 2.5em; margin-top: 15px; }
|
|
||||||
|
|
||||||
.site-url { font-size: 18px; margin-bottom: 8px; }
|
|
||||||
|
|
||||||
.site-url a { color: #e93250; font-weight: bold; }
|
|
||||||
|
|
||||||
.interior .header-Outro .subtitle { font-size: 1em; margin-top: -15px; }
|
|
||||||
|
|
||||||
.content.wide, .wide.footer-Content { padding-left: 6%; padding-right: 6%; }
|
|
||||||
|
|
||||||
.content.misc-page, .misc-page.footer-Content { background: #FAF6F1; -moz-box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.1); -webkit-box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.1); box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.1); padding: 1px 3% 40px 3%; }
|
|
||||||
|
|
||||||
.content.misc-page h3, .misc-page.footer-Content h3, .content.misc-page h4, .misc-page.footer-Content h4, .content.misc-page h5, .misc-page.footer-Content h5, .content.misc-page h6, .misc-page.footer-Content h6 { margin-top: 1em; }
|
|
||||||
|
|
||||||
.content.misc-page h2, .misc-page.footer-Content h2 { font-size: 1.6em; margin-top: 1em; }
|
|
||||||
|
|
||||||
.content.misc-page h3, .misc-page.footer-Content h3, .content.misc-page h4, .misc-page.footer-Content h4, .content.misc-page h5, .misc-page.footer-Content h5 { font-size: 1.2em; }
|
|
||||||
|
|
||||||
.content.misc-page h6, .misc-page.footer-Content h6 { font-size: 1em; }
|
|
||||||
|
|
||||||
.content.misc-page h1, .misc-page.footer-Content h1 { font-size: 2.2em; margin-bottom: 20px; }
|
|
||||||
|
|
||||||
.content.misc-page hr, .misc-page.footer-Content hr { margin: 35px 0 25px 0; }
|
|
||||||
|
|
||||||
.interior .header-Outro .col.col-50.signup-Area { padding-left: 0; }
|
|
||||||
|
|
||||||
.interior .header-Outro a { color: #E93250; }
|
|
||||||
|
|
||||||
.interior .header-Outro .btn-Action { color: #fff; }
|
|
||||||
|
|
||||||
.interior .signup-Area { min-height: 164px; }
|
|
||||||
|
|
||||||
.interior .signup-Area img { -moz-box-shadow: 1px 2px 5px 2px rgba(0, 0, 0, 0.1); -webkit-box-shadow: 1px 2px 5px 2px rgba(0, 0, 0, 0.1); box-shadow: 1px 2px 5px 2px rgba(0, 0, 0, 0.1); border: 4px solid white; }
|
|
||||||
|
|
||||||
.interior .signup-Area.large img { border: 6px solid white; }
|
|
||||||
|
|
||||||
.interior .header-Outro .screenshot { width: 235px; height: 141px; }
|
|
||||||
|
|
||||||
.interior .header-Outro ul { margin: 0; padding: 0; }
|
|
||||||
|
|
||||||
.interior .header-Outro ul li { list-style: none; font-size: 80%; margin-bottom: 2px; }
|
|
||||||
|
|
||||||
.interior .signup-Form fieldset { padding: 20px; }
|
|
||||||
|
|
||||||
.interior .header-Outro h2 { margin-top: 12px; font-size: 1.8em; }
|
|
||||||
|
|
||||||
.welcome { background: #daeea5 url(../img/heartcat.png) no-repeat 20px center; background-size: 77px 81px; padding: 20px 20px 20px 108px; margin-bottom: 30px; }
|
|
||||||
|
|
||||||
.welcome h4 { font-size: 1.2em; margin-bottom: 4px; }
|
|
||||||
|
|
||||||
.welcome .close-button { float: right; background: url(../img/welcome-close.png) no-repeat; width: 19px; height: 19px; }
|
|
||||||
|
|
||||||
.files { float: left; background: #E4D8CB; width: 100%; position: relative; }
|
|
||||||
|
|
||||||
.files .header { background: #5E95A1; color: #fff; float: left; width: 100%; padding: 10px 20px; }
|
|
||||||
|
|
||||||
.files .breadcrumbs { float: left; font-weight: bold; margin-top: 4px; }
|
|
||||||
|
|
||||||
.files .actions { float: right; }
|
|
||||||
|
|
||||||
.files .btn-Action { margin-left: 8px; }
|
|
||||||
|
|
||||||
.btn-Action span { background-repeat: no-repeat; }
|
|
||||||
|
|
||||||
.btn-Action.new-Page span { background-image: url(../img/new-page.png); background-position-y: 1px; padding-left: 29px; }
|
|
||||||
|
|
||||||
.btn-Action.new-Folder span { background-image: url(../img/new-folder.png); padding-left: 26px; }
|
|
||||||
|
|
||||||
.btn-Action.upload span { background-image: url(../img/upload.png); padding-left: 24px; }
|
|
||||||
|
|
||||||
.files .list { padding: 20px; }
|
|
||||||
|
|
||||||
.files .list .upload-Boundary { float: left; border: 3px dashed #F6F0E6; width: 100%; margin: 18px 0; padding: 10px; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; min-height: 300px; }
|
|
||||||
|
|
||||||
.files .list .upload-Boundary.with-instruction { background: url(../img/drag-drop.png) no-repeat center center; }
|
|
||||||
|
|
||||||
.files .uploading-overlay { width: 100%; height: 100%; position: absolute; background-color: rgba(0, 0, 0, 0.35); z-index: 100; }
|
|
||||||
|
|
||||||
.files .uploading { background: #fff; font-style: italic; margin-left: auto; margin-right: auto; width: 400px; margin-top: 14%; padding: 25px 40px 28px 40px; -webkit-box-shadow: 1px 1px 21px 5px rgba(50, 50, 50, 0.5); -moz-box-shadow: 1px 1px 21px 5px rgba(50, 50, 50, 0.5); box-shadow: 1px 1px 21px 5px rgba(50, 50, 50, 0.5); border-radius: 10px; }
|
|
||||||
|
|
||||||
.files .uploading p { margin-bottom: 2px; }
|
|
||||||
|
|
||||||
.files .progress-bar { background: #CCCCCC; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; width: 100%; position: relative; margin-top: 14px; height: 10px; overflow: hidden; }
|
|
||||||
|
|
||||||
.files .progress-bar .progress { background: #E93250; height: 100%; -webkit-border-top-right-radius: 0px; -webkit-border-bottom-right-radius: 0px; -moz-border-radius-topright: 0px; -moz-border-radius-bottomright: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; -webkit-border-top-left-radius: 20px; -webkit-border-bottom-left-radius: 20px; -moz-border-radius-topleft: 20px; -moz-border-radius-bottomleft: 20px; border-top-left-radius: 20px; border-bottom-left-radius: 20px; position: relative; overflow: hidden; display: block; }
|
|
||||||
|
|
||||||
.file { float: left; padding: 5px 0px; margin-right: 10px; margin-bottom: 10px; width: 125px; text-align: center; display: block; position: relative; }
|
|
||||||
|
|
||||||
.file .title { font-weight: bold; font-size: 12px; color: #666; margin-top: 4px; text-decoration: none; white-space: nowrap; overflow: hidden; display: block; }
|
|
||||||
|
|
||||||
.html-thumbnail { font-size: 11px; margin-top: 5px; margin-left: 10px; display: block; position: relative; width: 105px; height: 63px; }
|
|
||||||
|
|
||||||
.html-thumbnail.html img { width: 105px; height: 63px; -webkit-box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.3); -moz-box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.3); box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.3); }
|
|
||||||
|
|
||||||
.html-thumbnail.image img { max-width: 105px; max-height: 63px; width: auto; height: auto; }
|
|
||||||
|
|
||||||
.html-thumbnail.misc { width: 63px; height: 63px; margin-left: auto; margin-right: auto; }
|
|
||||||
|
|
||||||
.misc-icon { background: url(../img/misc-file.png) no-repeat 0px 0px; width: 67px; height: 67px; display: block; padding-top: 35px; font-size: 14px; color: #bbb; font-weight: bold; margin-left: auto; margin-right: auto; }
|
|
||||||
|
|
||||||
.overlay a { color: white; text-decoration: none; font-size: 14px; display: block; }
|
|
||||||
|
|
||||||
.overlay i { font-weight: bold; }
|
|
||||||
|
|
||||||
.overlay { position: absolute; top: 0; width: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: none; }
|
|
||||||
|
|
||||||
.link-overlay { display: block; position: absolute; bottom: 0px; left: 0px; width: 123px; height: 30px; }
|
|
||||||
|
|
||||||
.file > .overlay { -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; padding-top: 15px; background-color: rgba(0, 0, 0, 0.15); }
|
|
||||||
|
|
||||||
.filehover:hover .overlay, .fileimagehover:hover .overlay { display: block; }
|
|
||||||
|
|
||||||
.html-thumbnail.misc.fileimagehover .overlay { margin: 1px 0 0 2px; }
|
|
||||||
|
|
||||||
.site-actions { float: left; margin-top: 20px; font-size: 90%; }
|
|
||||||
|
|
||||||
.site-actions a { color: #666; }
|
|
||||||
|
|
||||||
@media (min-width: 1200px) { .container { width: 1200px; } }
|
|
||||||
@media (min-width: 700px) and (max-width: 1200px) { .container { width: 100%; } }
|
|
||||||
.content.misc-page.columns, .misc-page.columns.footer-Content { float: left; padding: 0; position: relative; clear: both; width: 100%; overflow: hidden; }
|
|
||||||
|
|
||||||
.col-left { float: left; width: 100%; position: relative; border-right: 1px solid #ddd; }
|
|
||||||
|
|
||||||
.right-col { background: #FAF6F1; }
|
|
||||||
|
|
||||||
.content.misc-page .col-33 h3, .misc-page.footer-Content .col-33 h3 { font-size: 1.5em; }
|
|
||||||
|
|
||||||
.content.misc-page .col-33 h3:nth-of-type(1), .misc-page.footer-Content .col-33 h3:nth-of-type(1) { margin-top: 0; }
|
|
||||||
|
|
||||||
.right-col .col-left { background-color: white; right: 33%; }
|
|
||||||
|
|
||||||
.content.misc-page.columns .col, .misc-page.columns.footer-Content .col { padding: 25px 30px 30px 30px; position: relative; overflow: hidden; }
|
|
||||||
|
|
||||||
.content.misc-page.columns .col-66, .misc-page.columns.footer-Content .col-66 { width: 67%; left: 33%; }
|
|
||||||
|
|
||||||
.content.misc-page.columns .col-33, .misc-page.columns.footer-Content .col-33 { width: 33%; left: 33%; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns { padding-top: 22px; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns h3 { float: left; margin-bottom: 0; font-size: 1.7em; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns .content, .interior .header-Outro.with-columns .footer-Content { padding: 0; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns .col { padding: 25px 0 8px 30px; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns .col-32 { width: 33%; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns .col-66 { width: 67%; border-right: 1px solid #0B0F11; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-columns .col-32 .edit { margin-top: 4px; float: right; margin-right: 30px; color: #fff; }
|
|
||||||
|
|
||||||
.feed-filter { float: left; margin-top: 1.4em; font-size: 0.8em; margin-left: 1.5em; }
|
|
||||||
|
|
||||||
.interior .header-Outro .feed-filter a { color: white; margin-left: 13px; }
|
|
||||||
|
|
||||||
.site-suggestion { float: left; width: 156px; margin-right: 20px; margin-bottom: 20px; height: 160px; }
|
|
||||||
|
|
||||||
.stats { margin-bottom: 2em; float: left; width: 100%; }
|
|
||||||
|
|
||||||
.content.misc-page.columns .stats .col, .misc-page.columns.footer-Content .stats .col { padding: 0; margin-bottom: 2em; }
|
|
||||||
|
|
||||||
.large-portrait { border: 10px solid white; -moz-box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.2); box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.2); }
|
|
||||||
|
|
||||||
.site-portrait { border: 5px solid white; -moz-box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.2); box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.2); float: left; margin-bottom: .5em; }
|
|
||||||
|
|
||||||
.site-portrait img { width: 146px; }
|
|
||||||
|
|
||||||
.site-portrait .caption { display: block; clear: both; font-size: .8em; margin-top: .2em; margin-bottom: -.2em; }
|
|
||||||
|
|
||||||
a.tag { font-size: .7em; text-transform: uppercase; background: #FFFFCC; color: #C1A009; float: left; padding: 1px 5px; -moz-box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.2); box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.2); margin-right: 3px; margin-right: 10px; margin-bottom: 7px; }
|
|
||||||
|
|
||||||
a.tag:hover { text-decoration: none; background: #FFEE8A; }
|
|
||||||
|
|
||||||
.following { width: 100%; float: left; margin-bottom: 2em; }
|
|
||||||
|
|
||||||
.avatar { height: 37px; width: 37px; margin: 0 4px 4px 0; float: left; }
|
|
||||||
|
|
||||||
.news-item { width: 100%; float: left; }
|
|
||||||
|
|
||||||
.news-item.update, .news-item.tags, .news-item.comment.for-me, .news-item.tip.for-me { margin: 1.8em 0; }
|
|
||||||
|
|
||||||
.news-item:nth-of-type(1) { margin-top: 0; }
|
|
||||||
|
|
||||||
.news-item.first { margin-top: 0; }
|
|
||||||
|
|
||||||
.news-item .user { font-weight: bold; color: #000; }
|
|
||||||
|
|
||||||
.news-item .comment { color: #000; font-style: normal; font-size: .8em; }
|
|
||||||
|
|
||||||
.news-item.for-me .comment { color: #000; font-style: normal; margin-top: .2em; font-size: .9em; margin-bottom: -.4em; }
|
|
||||||
|
|
||||||
.news-item .icon { width: 20px; height: 20px; background: #E6E6E6; float: left; margin-right: 12px; }
|
|
||||||
|
|
||||||
.news-item.comment .icon { background: #DAEEA5; }
|
|
||||||
|
|
||||||
.news-item.comment.for-me .icon, .news-item.tip.for-me .icon { background-size: 62px 62px; width: 82px; height: 62px; background-position: right top; background-repeat: no-repeat; }
|
|
||||||
|
|
||||||
.news-item.update .icon { background: #E93250; }
|
|
||||||
|
|
||||||
.news-item.tip .icon { background: #FFCC00; }
|
|
||||||
|
|
||||||
.news-item.follow .icon { background: #3399CC; }
|
|
||||||
|
|
||||||
.news-item .title { margin-bottom: .4em; position: relative; width: 100%; float: left; }
|
|
||||||
|
|
||||||
.news-item .date { float: right; color: #aaa; font-size: 11px; margin-top: .5em; }
|
|
||||||
|
|
||||||
.news-item .files { background: #eee; padding: 8px 10px 2px 10px; border-top: 1px solid #ddd; margin-bottom: 4px; }
|
|
||||||
|
|
||||||
.news-item .file { width: 90px; margin-bottom: 0; padding: 0; }
|
|
||||||
|
|
||||||
.news-item .file a:hover { text-decoration: none; }
|
|
||||||
|
|
||||||
.news-item .html-thumbnail { margin-top: 1px; margin-left: 0px; width: auto; }
|
|
||||||
|
|
||||||
.news-item .site-suggestion { height: auto; margin-bottom: 10px; }
|
|
||||||
|
|
||||||
.news-item .tag { float: none; margin-left: 4px; }
|
|
||||||
|
|
||||||
.news-item .actions { font-size: 11px; }
|
|
||||||
|
|
||||||
.news-item .actions a { margin-right: 6px; }
|
|
||||||
|
|
||||||
.news-item .content, .news-item .footer-Content { padding: 0 0 0 32px; }
|
|
||||||
|
|
||||||
.news-item .comments { margin-bottom: 1.5em; margin-top: .7em; }
|
|
||||||
|
|
||||||
.news-item .comments .comment { font-size: .8em; clear: both; }
|
|
||||||
|
|
||||||
.news-item .comments .comment .user { margin-right: 5px; }
|
|
||||||
|
|
||||||
.news-item .comments .comment .actions { margin-top: .3em; }
|
|
||||||
|
|
||||||
.news-item .avatar { margin-right: 10px; }
|
|
||||||
|
|
||||||
.signup-Area.large { width: 418px; height: 236px; }
|
|
||||||
|
|
||||||
.interior .header-Outro.with-site-image { padding-top: 20px; }
|
|
||||||
|
|
||||||
.report { margin-top: 2em; float: left; width: 100%; font-size: .8em; }
|
|
||||||
|
|
||||||
.report, .report a { color: #999; }
|
|
||||||
|
|
||||||
.interior .header-Outro .actions a { margin-right: 6px; }
|
|
||||||
|
|
||||||
.interior .header-Outro .stats { margin-bottom: 1.2em; float: left; width: 100%; margin-top: 2em; }
|
|
||||||
|
|
||||||
.interior .header-Outro .stats strong { font-size: 1.5em; font-weight: bold; color: #DAEEA5; }
|
|
||||||
|
|
||||||
.interior .header-Outro .stats span { font-size: .7em; text-transform: uppercase; clear: both; display: block; }
|
|
||||||
|
|
||||||
.interior .header-Outro .stats .stat { float: left; width: 90px; text-align: center; }
|
|
||||||
|
|
||||||
.interior .header-Outro .stats .stat.tips { width: 60px; }
|
|
||||||
|
|
||||||
.btn-Action.follow span { background-image: url(../img/follow.png); padding-left: 22px; }
|
|
||||||
|
|
||||||
.btn-Action.tip span { background-image: url(../img/tip.png); padding-left: 26px; background-position-y: -1px; }
|
|
||||||
|
|
||||||
.btn-Action.share span { background-image: url(../img/share.png); padding-left: 26px; }
|
|
||||||
|
|
||||||
.col-33 .stats .stat { margin-bottom: .4em; }
|
|
||||||
|
|
||||||
.col-33 .stats .stat span { width: 10em; float: left; }
|
|
||||||
|
|
||||||
.archives { float: left; width: 100%; margin-bottom: 2em; }
|
|
||||||
|
|
||||||
.archives img { float: left; border: 3px solid white; -webkit-box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.3); -moz-box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.3); box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.3); width: 72px; }
|
|
||||||
|
|
||||||
.archives img { margin-right: 10px; }
|
|
||||||
|
|
||||||
.more { float: left; clear: both; margin-top: 1em; font-size: .8em; }
|
|
||||||
|
|
||||||
.post-comment { background: #DAEEA5; float: left; width: 111%; padding: 20px 0 10px 30px; margin: -25px 0 28px -30px; }
|
|
||||||
|
|
||||||
.post-comment input { width: 84%; float: left; }
|
|
||||||
|
|
||||||
.post-comment .btn-Action { margin-left: 10px; margin-top: 4px; }
|
|
||||||
|
|
||||||
.supporter-badge { background: url(../img/supporter.png); width: 99px; height: 26px; float: left; margin-top: 7px; margin-left: 10px; }
|
|
||||||
|
|
||||||
.title-with-badge { float: left; width: 100%; }
|
|
||||||
|
|
||||||
.title-with-badge span { float: left; }
|
|
||||||
|
|
||||||
.section.featured-Websites { background: #65A0AD; }
|
|
||||||
|
|
||||||
.section.featured-Websites h2 { color: white; }
|
|
||||||
|
|
||||||
.section { padding: 50px 10%; margin: 0 auto; }
|
|
||||||
|
|
||||||
.section.previews { background: #f6f0e6; }
|
|
||||||
|
|
||||||
.section.previews h2, .section.previews p { color: #31424B; }
|
|
||||||
|
|
||||||
.section h2 { font-size: 1.6em; }
|
|
||||||
|
|
||||||
.section.last { background: #666666; }
|
|
||||||
|
|
||||||
.section.last h2, .section.last blockquote { color: white; }
|
|
||||||
|
|
||||||
.selected { font-weight: bold; }
|
|
||||||
|
|
||||||
.modal-body { max-height: 800px; overflow-y: visible; }
|
|
||||||
|
|
||||||
.tt-dropdown-menu { padding: 0px 10px 0px 10px; background: #FFFFFF; }
|
|
||||||
|
|
||||||
.footer-Base { color: #5e5b56; float: left; width: 100%; }
|
|
||||||
.footer-Base h1, .footer-Base h2, .footer-Base h3, .footer-Base h4 { color: #8b9a7a; }
|
|
||||||
|
|
||||||
.footer-Intro { background: #daeea5; border-top: 1px solid #cedbab; -moz-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); position: relative; }
|
|
||||||
|
|
||||||
.footer-Content { padding-top: 20px; padding-bottom: 20px; padding-left: 40px; padding-right: 20px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .footer-Content { padding-left: 20px; } }
|
|
||||||
|
|
||||||
.footer-Content .row, .footer-Content .c-Row { margin-left: 0; }
|
|
||||||
|
|
||||||
.f-Col { -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; min-height: 125px; padding-bottom: 28px; position: relative; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .f-Col { min-height: 2px; padding-left: 15px !important; padding-right: 100px !important; } }
|
|
||||||
.f-Col .action-Link { bottom: 0; position: absolute; right: 12px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .f-Col .action-Link { position: static; } }
|
|
||||||
|
|
||||||
.footer-icon { background-repeat: no-repeat; position: absolute; right: 0; top: -70px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .footer-icon { -moz-background-size: 100%; -ms-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; height: 90px !important; top: 0; width: 77px !important; } }
|
|
||||||
|
|
||||||
.f-Col-1 { padding-right: 12px; }
|
|
||||||
.f-Col-1 .footer-icon { background-image: url(../img/support-us.png); height: 104px; right: 5px; width: 92px; }
|
|
||||||
|
|
||||||
.f-Col-2 { padding-left: 15px; padding-right: 6px; position: relative; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .f-Col-2 { border-bottom: 1px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, 0.1); margin: 20px 0; overflow: hidden; padding: 20px 0; }
|
|
||||||
.f-Col-2 .footer-icon { top: 14px; } }
|
|
||||||
.f-Col-2:before, .f-Col-2:after { background: url("../img/border.png") no-repeat 0 -20px; content: ""; height: 200px; opacity: 0.2; position: absolute; top: 0; width: 1px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .f-Col-2:before, .f-Col-2:after { display: none; } }
|
|
||||||
.f-Col-2:before { left: -14px; }
|
|
||||||
.f-Col-2:after { right: -14px; }
|
|
||||||
.f-Col-2 .footer-icon { background-image: url(../img/about-neocities.png); width: 100px; height: 106px; }
|
|
||||||
|
|
||||||
.f-Col-3 { padding-left: 20px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .f-Col-3 { padding-left: 0; } }
|
|
||||||
.f-Col-3 .footer-icon { background-image: url(../img/latest-news.png); height: 103px; width: 134px; }
|
|
||||||
|
|
||||||
.footer-Outro { background: #ccdf9b; border-top: 1px solid #b3c388; overflow: hidden; }
|
|
||||||
.footer-Outro a { color: #5e5b56; }
|
|
||||||
|
|
||||||
.credits { margin-bottom: 0; }
|
|
||||||
|
|
||||||
.footer-Nav { text-transform: uppercase; }
|
|
||||||
.footer-Nav .h-Nav, .footer-Nav .bread { float: right; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .footer-Nav .h-Nav, .footer-Nav .bread { float: none; margin-top: 15px; }
|
|
||||||
.footer-Nav .h-Nav li:first-child a, .footer-Nav .bread li:first-child a { padding-left: 0; } }
|
|
||||||
.footer-Nav .h-Nav a, .footer-Nav .bread a { padding: 0 8px; }
|
|
||||||
.footer-Nav .h-Nav li:last-child a, .footer-Nav .bread li:last-child a { padding-right: 0; }
|
|
||||||
|
|
||||||
.alert { background-color: #F5BA00; color: #fff; }
|
|
||||||
|
|
||||||
.website-Gallery { list-style: none; padding: 10px 0; }
|
|
||||||
.website-Gallery li { float: left; margin-bottom: 8px; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .website-Gallery li { width: 50%; } }
|
|
||||||
.website-Gallery a { padding: 0 8px; display: block; }
|
|
||||||
.website-Gallery.int-Gall li { border: 1px solid #ccc; margin: 0 .5% 12px; width: 24%; }
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) { .website-Gallery.int-Gall li { width: 49%; } }
|
|
||||||
.website-Gallery.int-Gall li a { padding: 8px; }
|
|
||||||
|
|
||||||
.neo-SS, .neo-Screen-Shot { background: #fff; -moz-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 8px -8px rgba(0, 0, 0, 0.2); display: block; height: auto !important; padding: 8px; position: relative; width: 100%; }
|
|
||||||
|
|
||||||
.img-Holder { -moz-background-size: cover !important; -webkit-background-size: cover !important; background-size: cover !important; display: block; }
|
|
||||||
|
|
||||||
.hp-Gallery img, .neo-Screen-Shot img { width: 100%; }
|
|
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'
|
require_relative './environment.rb'
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
describe 'signup' do
|
describe 'signup' do
|
||||||
include Capybara::DSL
|
include Capybara::DSL
|
||||||
|
@ -215,56 +158,4 @@ describe 'signup' do
|
||||||
page.must_have_content 'Your Feed'
|
page.must_have_content 'Your Feed'
|
||||||
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
||||||
end
|
end
|
||||||
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
|
|
|
@ -84,17 +84,17 @@ describe 'api delete' do
|
||||||
res[:error_type].must_equal 'cannot_delete_index'
|
res[:error_type].must_equal 'cannot_delete_index'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails with bad filename' do
|
it 'succeeds with weird filenames' do
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
@site.store_file 't$st.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
@site.store_file 't$st.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
post '/api/delete', filenames: ['t$st.jpg']
|
post '/api/delete', filenames: ['t$st.jpg']
|
||||||
res[:error_type].must_equal 'bad_filename'
|
res[:result].must_equal 'success'
|
||||||
|
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
post '/api/delete', filenames: ['./config.yml']
|
post '/api/delete', filenames: ['./config.yml']
|
||||||
res[:error_type].must_equal 'bad_filename'
|
res[:error_type].must_equal 'missing_files'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails with missing files' do
|
it 'fails with missing files' do
|
||||||
|
@ -137,13 +137,59 @@ describe 'api upload' do
|
||||||
res[:error_type].must_equal 'missing_files'
|
res[:error_type].must_equal 'missing_files'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails for invalid filenames' do
|
it 'resists directory traversal attack' do
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
post '/api/upload', {
|
post '/api/upload', {
|
||||||
'../lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
'../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
|
end
|
||||||
|
|
||||||
it 'fails for invalid files' do
|
it 'fails for invalid files' do
|
||||||
|
@ -180,7 +226,7 @@ describe 'api upload' do
|
||||||
end
|
end
|
||||||
|
|
||||||
def site_file_exists?(file)
|
def site_file_exists?(file)
|
||||||
File.exist?(@site.file_path('test.jpg'))
|
File.exist?(@site.files_path('test.jpg'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def res
|
def res
|
||||||
|
|
|
@ -9,18 +9,26 @@ end
|
||||||
|
|
||||||
SimpleCov.command_name 'minitest'
|
SimpleCov.command_name 'minitest'
|
||||||
|
|
||||||
|
require 'rack_session_access'
|
||||||
require './environment'
|
require './environment'
|
||||||
require 'webmock'
|
|
||||||
include WebMock::API
|
|
||||||
require './app'
|
require './app'
|
||||||
|
|
||||||
Bundler.require :test
|
Bundler.require :test
|
||||||
|
|
||||||
#require 'minitest/pride'
|
#require 'minitest/pride'
|
||||||
require 'minitest/autorun'
|
require 'minitest/autorun'
|
||||||
|
require 'webmock'
|
||||||
|
include WebMock::API
|
||||||
|
require 'webmock/minitest'
|
||||||
require 'sidekiq/testing'
|
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
|
Site.bcrypt_cost = BCrypt::Engine::MIN_COST
|
||||||
|
|
||||||
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
|
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
|
||||||
|
|
8
tests/files/index.html
Normal file
8
tests/files/index.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Hello?</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hi there!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
80
tests/site_file_tests.rb
Normal file
80
tests/site_file_tests.rb
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
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 index.html file' do
|
||||||
|
site = Fabricate :site
|
||||||
|
PurgeCacheWorker.jobs.clear
|
||||||
|
ScreenshotWorker.jobs.clear
|
||||||
|
|
||||||
|
post '/site_files/upload', {
|
||||||
|
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'text/html'),
|
||||||
|
'csrf_token' => 'abcd'
|
||||||
|
}, {'rack.session' => { 'id' => site.id, '_csrf_token' => 'abcd' }}
|
||||||
|
last_response.body.must_match /successfully uploaded/i
|
||||||
|
File.exists?(site.files_path('index.html')).must_equal true
|
||||||
|
|
||||||
|
args = ScreenshotWorker.jobs.first['args']
|
||||||
|
args.first.must_equal site.username
|
||||||
|
args.last.must_equal 'index.html'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds with valid file' do
|
||||||
|
site = Fabricate :site
|
||||||
|
PurgeCacheWorker.jobs.clear
|
||||||
|
ThumbnailWorker.jobs.clear
|
||||||
|
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
|
||||||
|
|
||||||
|
queue_args = PurgeCacheWorker.jobs.first['args'].first
|
||||||
|
queue_args['site'].must_equal site.username
|
||||||
|
queue_args['path'].must_equal '/test.jpg'
|
||||||
|
|
||||||
|
ThumbnailWorker.jobs.length.must_equal 1
|
||||||
|
ThumbnailWorker.drain
|
||||||
|
|
||||||
|
Site::THUMBNAIL_RESOLUTIONS.each do |resolution|
|
||||||
|
File.exists?(site.thumbnail_path('test.jpg', resolution)).must_equal true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works with directory path' do
|
||||||
|
site = Fabricate :site
|
||||||
|
ThumbnailWorker.jobs.clear
|
||||||
|
PurgeCacheWorker.jobs.clear
|
||||||
|
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
|
||||||
|
|
||||||
|
PurgeCacheWorker.jobs.length.must_equal 1
|
||||||
|
queue_args = PurgeCacheWorker.jobs.first['args'].first
|
||||||
|
queue_args['path'].must_equal '/derpie/derptest/test.jpg'
|
||||||
|
|
||||||
|
ThumbnailWorker.jobs.length.must_equal 1
|
||||||
|
ThumbnailWorker.drain
|
||||||
|
|
||||||
|
Site::THUMBNAIL_RESOLUTIONS.each do |resolution|
|
||||||
|
File.exists?(site.thumbnail_path('derpie/derptest/test.jpg', resolution)).must_equal true
|
||||||
|
site.thumbnail_url('derpie/derptest/test.jpg', resolution).must_equal(
|
||||||
|
File.join "#{Site::THUMBNAILS_URL_ROOT}", site.username, "/derpie/derptest/test.jpg.#{resolution}.jpg"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
tests/workers/screenshot_worker_tests.rb
Normal file
28
tests/workers/screenshot_worker_tests.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
require_relative '../environment.rb'
|
||||||
|
|
||||||
|
describe ScreenshotWorker do
|
||||||
|
|
||||||
|
it 'saves a screenshot for a root html file' do
|
||||||
|
worker = ScreenshotWorker.new
|
||||||
|
worker.perform 'kyledrake', 'index.html'
|
||||||
|
site = Fabricate :site
|
||||||
|
Site::SCREENSHOT_RESOLUTIONS.each do |r|
|
||||||
|
File.exists?(File.join(Site::SCREENSHOTS_ROOT, 'kyledrake', "index.html.#{r}.jpg")).must_equal true
|
||||||
|
site.screenshot_url('index.html', r).must_equal(
|
||||||
|
File.join(Site::SCREENSHOTS_URL_ROOT, site.username, "index.html.#{r}.jpg")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'saves a screenshot for a path html file' do
|
||||||
|
worker = ScreenshotWorker.new
|
||||||
|
worker.perform 'kyledrake', 'derpie/derp/index.html'
|
||||||
|
site = Fabricate :site
|
||||||
|
Site::SCREENSHOT_RESOLUTIONS.each do |r|
|
||||||
|
File.exists?(File.join(Site::SCREENSHOTS_ROOT, 'kyledrake', "derpie/derp/index.html.#{r}.jpg")).must_equal true
|
||||||
|
site.screenshot_url('derpie/derp/index.html', r).must_equal(
|
||||||
|
File.join(Site::SCREENSHOTS_URL_ROOT, site.username, "derpie/derp/index.html.#{r}.jpg")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -34,7 +34,7 @@
|
||||||
</li>
|
</li>
|
||||||
<% else %>
|
<% else %>
|
||||||
<li>
|
<li>
|
||||||
<a href="/dashboard" class="sign-In">Dashboard</a>
|
<a href="/dashboard" class="sign-In">Edit Site</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/settings" class="sign-In">Settings</a>
|
<a href="/settings" class="sign-In">Settings</a>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
<%= File.extname(f).sub('.', '') %>
|
<%= File.extname(f).sub('.', '') %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="title"><%= f %></span>
|
<span class="title" title="<%= f %>"><%= f %></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -73,7 +73,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="site-info">
|
<div class="site-info">
|
||||||
<div style="float: left">
|
<div style="float: left">
|
||||||
<a href="/site/<%= site.username %>" title="Profile"><i class="icon-user"></i> <%= site.username %></a>
|
<a href="/site/<%= site.username %>" title="Profile">
|
||||||
|
<i class="icon-user"></i>
|
||||||
|
<%= site.username %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<a href="/site/<%= site.username %>">
|
<a href="/site/<%= site.username %>">
|
||||||
|
|
|
@ -15,14 +15,6 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</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="header-Outro with-site-image">
|
||||||
<div class="row content wide">
|
<div class="row content wide">
|
||||||
|
@ -88,10 +80,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="breadcrumbs">Home</div>
|
<div class="breadcrumbs">
|
||||||
|
<% if params[:dir].nil? || params[:dir].empty? || params[:dir] == '/' %>
|
||||||
|
Home
|
||||||
|
<% else %>
|
||||||
|
<% puts params[:dir].inspect %>
|
||||||
|
<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 %>"><%= dir %></a> /
|
||||||
|
<% else %>
|
||||||
|
<%= dir %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/site_files/new_page" class="btn-Action new-Page"><span>New Page</span></a>
|
<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>
|
<a href="#" class="btn-Action upload" onclick="clickUploadFiles(); return false"><span>Upload</span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,47 +109,49 @@
|
||||||
<form action="/site_files/upload" class="dropzone" id="uploads">
|
<form action="/site_files/upload" class="dropzone" id="uploads">
|
||||||
<div class="dz-message" style="display: none"></div>
|
<div class="dz-message" style="display: none"></div>
|
||||||
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||||
<div class="upload-Boundary <%= current_site.file_list.length <= 5 ? 'with-instruction' : '' %>">
|
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||||
<% current_site.file_list.each do |file| %>
|
<% @file_list.each do |file| %>
|
||||||
<div class="file filehover">
|
<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">
|
<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 class="overlay"></div>
|
||||||
</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">
|
<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 class="overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- if folder
|
<% elsif file[:is_directory] %>
|
||||||
<div class="html-thumbnail folder fileimagehover">
|
<div class="html-thumbnail folder fileimagehover">
|
||||||
<div class="folder-icon"></div>
|
<div class="folder-icon"></div>
|
||||||
<div class="overlay"></div>
|
<div class="overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="html-thumbnail misc fileimagehover">
|
<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 class="overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<a class="title">
|
<a class="title">
|
||||||
<% if file.filename.length > 14 %>
|
<% if file[:name].length > 15 %>
|
||||||
<%= file.filename.slice(0..14) %>…
|
<%= file[:name].slice(0..14) %>…
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= file.filename %>
|
<%= file[:name] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
<div class="overlay">
|
<div class="overlay">
|
||||||
<% if file.ext.match(/html|htm|txt|js|css|md/) %>
|
<% if file[:is_editable] %>
|
||||||
<a href="/site_files/text_editor/<%= file.filename %>"><i class="icon-edit" title="Edit"> Edit</i></a>
|
<a href="/site_files/text_editor<%= file[:path] %>"><i class="icon-edit" title="Edit"> Edit</i></a>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if file.filename != 'index.html' %>
|
<% if file[:is_directory] %>
|
||||||
<a href="#" onclick="confirmFileDelete('<%= file.filename %>')"><i class="icon-trash" title="Delete"> Delete</i></a>
|
<a href="?dir=<%= Rack::Utils.escape file[:path] %>"><i class="icon-edit" title="Manage"> Manage</i></a>
|
||||||
<% end %>
|
<% 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>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -158,7 +170,7 @@
|
||||||
<h3 id="deleteConfirmModalLabel">Confirm deletion</h3>
|
<h3 id="deleteConfirmModalLabel">Confirm deletion</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<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>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
@ -174,9 +186,10 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</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="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||||
<input name="from_button" type="hidden" value="true">
|
<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()">
|
<input id="uploadFiles" type="file" name="files[]" multiple onchange="$('#uploadFilesButtonForm').submit()">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -224,7 +237,6 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
||||||
console.log('OH HI')
|
|
||||||
showUploadProgress()
|
showUploadProgress()
|
||||||
$('#progressBar').css('display', 'block')
|
$('#progressBar').css('display', 'block')
|
||||||
$('#uploadingProgress').css('width', progress+'%')
|
$('#uploadingProgress').css('width', progress+'%')
|
||||||
|
@ -232,3 +244,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<div class="header-Outro">
|
<div class="header-Outro">
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
<h1>Editing <%= params[:filename] %></h1>
|
<h1>Editing <%= @filename %></h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -120,11 +120,14 @@
|
||||||
|
|
||||||
function saveTextFile(quit) {
|
function saveTextFile(quit) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/site_files/save/<%= params[:filename] %>?csrf_token=<%= csrf_token %>',
|
url: '/site_files/save/<%= @filename %>?csrf_token=<%= csrf_token %>',
|
||||||
data: editor.getValue(),
|
data: editor.getValue(),
|
||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
alert('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!')
|
||||||
|
},
|
||||||
success: function(response){
|
success: function(response){
|
||||||
if(response == 'ok') {
|
if(response == 'ok') {
|
||||||
if(quit === true) {
|
if(quit === true) {
|
||||||
|
|
|
@ -27,24 +27,25 @@ module Phantomjs
|
||||||
end
|
end
|
||||||
|
|
||||||
class ScreenshotWorker
|
class ScreenshotWorker
|
||||||
SCREENSHOTS_PATH = File.join DIR_ROOT, 'public', 'site_screenshots'
|
SCREENSHOTS_PATH = Site::SCREENSHOTS_ROOT
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
sidekiq_options queue: :screenshots, retry: 3, backtrace: true
|
sidekiq_options queue: :screenshots, retry: 3, backtrace: true
|
||||||
|
|
||||||
def perform(username, filename)
|
def perform(username, path)
|
||||||
|
path = "/#{path}" unless path[0] == '/'
|
||||||
screenshot = Tempfile.new 'neocities_screenshot'
|
screenshot = Tempfile.new 'neocities_screenshot'
|
||||||
screenshot.close
|
screenshot.close
|
||||||
screenshot_output_path = screenshot.path+'.png'
|
screenshot_output_path = screenshot.path+'.png'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
f = Screencap::Fetcher.new("http://#{username}.neocities.org/#{filename}")
|
f = Screencap::Fetcher.new("http://#{username}.neocities.org#{path}")
|
||||||
f.fetch(
|
f.fetch(
|
||||||
output: screenshot_output_path,
|
output: screenshot_output_path,
|
||||||
width: 1280,
|
width: 1280,
|
||||||
height: 720
|
height: 720
|
||||||
)
|
)
|
||||||
rescue Timeout::Error
|
rescue Timeout::Error
|
||||||
puts "#{username}/#{filename} is timing out, discontinuing"
|
puts "#{username}/#{path} is timing out, discontinuing"
|
||||||
site = Site[username: username]
|
site = Site[username: username]
|
||||||
site.update is_crashing: true
|
site.update is_crashing: true
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ class ScreenshotWorker
|
||||||
EmailWorker.perform_async({
|
EmailWorker.perform_async({
|
||||||
from: 'web@neocities.org',
|
from: 'web@neocities.org',
|
||||||
to: site.email,
|
to: site.email,
|
||||||
subject: "[NeoCities] The web page \"#{filename}\" on your site (#{username}.neocities.org) is slow",
|
subject: "[NeoCities] The web page \"#{path}\" on your site (#{username}.neocities.org) is slow",
|
||||||
body: "Hi there! This is an automated email to inform you that we're having issues loading your site to take a "+
|
body: "Hi there! This is an automated email to inform you that we're having issues loading your site to take a "+
|
||||||
"screenshot. It is possible that this is an error specific to our screenshot program, but it is much more "+
|
"screenshot. It is possible that this is an error specific to our screenshot program, but it is much more "+
|
||||||
"likely that your site is too slow to be used with browsers. We don't want Neocities sites crashing browsers, "+
|
"likely that your site is too slow to be used with browsers. We don't want Neocities sites crashing browsers, "+
|
||||||
|
@ -79,10 +80,12 @@ class ScreenshotWorker
|
||||||
img = img_list.reverse.flatten_images
|
img = img_list.reverse.flatten_images
|
||||||
|
|
||||||
user_screenshots_path = File.join SCREENSHOTS_PATH, username
|
user_screenshots_path = File.join SCREENSHOTS_PATH, username
|
||||||
FileUtils.mkdir_p user_screenshots_path
|
screenshot_path = File.join user_screenshots_path, File.dirname(path)
|
||||||
|
|
||||||
|
FileUtils.mkdir_p screenshot_path unless Dir.exists?(screenshot_path)
|
||||||
|
|
||||||
Site::SCREENSHOT_RESOLUTIONS.each do |res|
|
Site::SCREENSHOT_RESOLUTIONS.each do |res|
|
||||||
img.scale(*res.split('x').collect {|r| r.to_i}).write(File.join(user_screenshots_path, "#{filename}.#{res}.jpg")) {
|
img.scale(*res.split('x').collect {|r| r.to_i}).write(File.join(user_screenshots_path, "#{path}.#{res}.jpg")) {
|
||||||
self.quality = 90
|
self.quality = 90
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
require 'RMagick'
|
require 'RMagick'
|
||||||
|
|
||||||
class ThumbnailWorker
|
class ThumbnailWorker
|
||||||
THUMBNAILS_PATH = File.join DIR_ROOT, 'public', 'site_thumbnails'
|
THUMBNAILS_PATH = Site::THUMBNAILS_ROOT
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
sidekiq_options queue: :thumbnails, retry: 3, backtrace: true
|
sidekiq_options queue: :thumbnails, retry: 3, backtrace: true
|
||||||
|
|
||||||
def perform(username, filename)
|
def perform(username, path)
|
||||||
img_list = Magick::ImageList.new
|
img_list = Magick::ImageList.new
|
||||||
img_list.from_blob File.read(File.join(Site::SITE_FILES_ROOT, username, filename))
|
img_list.from_blob File.read(File.join(Site::SITE_FILES_ROOT, username, path))
|
||||||
img = img_list.first
|
img = img_list.first
|
||||||
|
|
||||||
user_thumbnails_path = File.join THUMBNAILS_PATH, username
|
user_thumbnails_path = File.join THUMBNAILS_PATH, username
|
||||||
FileUtils.mkdir_p user_thumbnails_path
|
FileUtils.mkdir_p user_thumbnails_path
|
||||||
|
FileUtils.mkdir_p File.join(user_thumbnails_path, File.dirname(path))
|
||||||
|
|
||||||
Site::THUMBNAIL_RESOLUTIONS.each do |res|
|
Site::THUMBNAIL_RESOLUTIONS.each do |res|
|
||||||
resimg = img.resize_to_fit(*res.split('x').collect {|r| r.to_i})
|
resimg = img.resize_to_fit(*res.split('x').collect {|r| r.to_i})
|
||||||
format = File.extname(filename).gsub('.', '')
|
format = File.extname(path).gsub('.', '')
|
||||||
|
|
||||||
save_ext = format.match(Site::LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
save_ext = format.match(Site::LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||||
|
|
||||||
resimg.write(File.join(user_thumbnails_path, "#{filename}.#{res}.#{save_ext}")) {
|
resimg.write(File.join(user_thumbnails_path, "#{path}.#{res}.#{save_ext}")) {
|
||||||
self.quality = 90
|
self.quality = 90
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue