mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +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
|
||||
config.yml
|
||||
.DS_Store
|
||||
public/assets/css/.sass-cache/
|
||||
public/assets/css/neo.css
|
||||
public/site_thumbnails
|
||||
public/sites
|
||||
public/site_screenshots
|
||||
public/site_screenshots_test
|
||||
public/site_thumbnails_test
|
||||
*.swp
|
||||
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-xsendfile', require: 'sinatra/xsendfile'
|
||||
gem 'puma', require: nil
|
||||
gem 'rubyzip', require: 'zip'
|
||||
gem 'rack-recaptcha', require: 'rack/recaptcha'
|
||||
gem 'rmagick', require: nil
|
||||
gem 'sidekiq'
|
||||
|
@ -19,6 +18,10 @@ gem 'erubis'
|
|||
gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'
|
||||
gem 'screencap'
|
||||
gem 'cocaine'
|
||||
gem 'zipruby'
|
||||
gem 'always_verify_ssl_certificates'
|
||||
gem 'sass', require: nil
|
||||
gem 'dav4rack'
|
||||
|
||||
platform :mri do
|
||||
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
||||
|
@ -45,7 +48,6 @@ end
|
|||
|
||||
group :development do
|
||||
gem 'shotgun', require: nil
|
||||
gem 'sass', require: nil
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
@ -54,13 +56,13 @@ group :test do
|
|||
gem 'minitest'
|
||||
gem 'minitest-reporters', require: 'minitest/reporters'
|
||||
gem 'rack-test', require: 'rack/test'
|
||||
gem 'webmock'
|
||||
gem 'mocha', require: nil
|
||||
gem 'rake', require: nil
|
||||
gem 'poltergeist'
|
||||
gem 'phantomjs', require: 'phantomjs/poltergeist'
|
||||
gem 'capybara'
|
||||
gem 'capybara_minitest_spec'
|
||||
gem 'rack_session_access', require: nil
|
||||
gem 'webmock', require: nil
|
||||
|
||||
platform :mri do
|
||||
gem 'simplecov', require: nil
|
||||
|
|
34
Gemfile.lock
34
Gemfile.lock
|
@ -18,6 +18,7 @@ GEM
|
|||
tzinfo (~> 1.1)
|
||||
addressable (2.3.6)
|
||||
ago (0.1.5)
|
||||
always_verify_ssl_certificates (0.3.0)
|
||||
ansi (1.4.3)
|
||||
autoparse (0.3.3)
|
||||
addressable (>= 2.3.1)
|
||||
|
@ -25,7 +26,7 @@ GEM
|
|||
multi_json (>= 1.0.0)
|
||||
bcrypt (3.1.7)
|
||||
builder (3.2.2)
|
||||
capybara (2.2.1)
|
||||
capybara (2.4.1)
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
|
@ -42,16 +43,20 @@ GEM
|
|||
cocaine (0.5.4)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.0)
|
||||
columnize (0.3.6)
|
||||
columnize (0.8.9)
|
||||
connection_pool (2.0.0)
|
||||
crack (0.4.2)
|
||||
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)
|
||||
columnize (>= 0.3.1)
|
||||
debugger-linecache (~> 1.2.0)
|
||||
debugger-ruby_core_source (~> 1.3.2)
|
||||
debugger-linecache (1.2.0)
|
||||
debugger-ruby_core_source (1.3.2)
|
||||
debugger-ruby_core_source (1.3.5)
|
||||
docile (1.1.3)
|
||||
erubis (2.7.0)
|
||||
extlib (0.9.16)
|
||||
|
@ -89,7 +94,7 @@ GEM
|
|||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.5.3)
|
||||
mini_portile (0.6.0)
|
||||
minitest (5.3.1)
|
||||
minitest-reporters (1.0.2)
|
||||
ansi
|
||||
|
@ -98,13 +103,13 @@ GEM
|
|||
powerbar
|
||||
mocha (1.0.0)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.9.2)
|
||||
multi_json (1.10.1)
|
||||
multipart-post (2.0.0)
|
||||
nokogiri (1.6.1)
|
||||
mini_portile (~> 0.5.0)
|
||||
nokogiri (1.6.3.1)
|
||||
mini_portile (= 0.6.0)
|
||||
pg (0.17.1)
|
||||
phantomjs (1.9.7.0)
|
||||
poltergeist (1.5.0)
|
||||
poltergeist (1.5.1)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -129,6 +134,9 @@ GEM
|
|||
json
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rack_session_access (0.1.1)
|
||||
builder (>= 2.0.0)
|
||||
rack (>= 1.0.0)
|
||||
rainbows (4.6.1)
|
||||
kgio (~> 2.5)
|
||||
rack (~> 1.1)
|
||||
|
@ -142,7 +150,6 @@ GEM
|
|||
mime-types (>= 1.16)
|
||||
retriable (1.4.1)
|
||||
rmagick (2.13.2)
|
||||
rubyzip (1.1.2)
|
||||
safe_yaml (1.0.1)
|
||||
sass (3.3.8)
|
||||
screencap (0.1.1)
|
||||
|
@ -194,19 +201,21 @@ GEM
|
|||
webmock (1.17.4)
|
||||
addressable (>= 2.2.7)
|
||||
crack (>= 0.3.2)
|
||||
websocket-driver (0.3.2)
|
||||
websocket-driver (0.3.4)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
zipruby (0.3.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
ago
|
||||
always_verify_ssl_certificates
|
||||
bcrypt
|
||||
capybara
|
||||
capybara_minitest_spec
|
||||
cocaine
|
||||
dav4rack
|
||||
erubis
|
||||
fabrication
|
||||
faker
|
||||
|
@ -228,12 +237,12 @@ DEPENDENCIES
|
|||
puma
|
||||
rack-recaptcha
|
||||
rack-test
|
||||
rack_session_access
|
||||
rainbows
|
||||
rake
|
||||
redis
|
||||
rmagick
|
||||
ruby-debug
|
||||
rubyzip
|
||||
sass
|
||||
screencap
|
||||
sequel (= 4.8.0)
|
||||
|
@ -247,3 +256,4 @@ DEPENDENCIES
|
|||
stripe!
|
||||
tilt
|
||||
webmock
|
||||
zipruby
|
||||
|
|
4
Rakefile
4
Rakefile
|
@ -7,7 +7,7 @@ end
|
|||
desc "Run all tests"
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << "spec"
|
||||
t.test_files = FileList['tests/*_tests.rb']
|
||||
t.test_files = FileList['tests/**/*_tests.rb']
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
|
@ -87,4 +87,4 @@ end
|
|||
desc 'Compile domain map for nginx'
|
||||
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" }}
|
||||
end
|
||||
end
|
||||
|
|
95
app.rb
95
app.rb
|
@ -452,6 +452,17 @@ end
|
|||
|
||||
get '/dashboard' do
|
||||
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'
|
||||
end
|
||||
|
||||
|
@ -562,7 +573,7 @@ post '/change_name' do
|
|||
end
|
||||
|
||||
old_host = current_site.host
|
||||
old_file_paths = current_site.file_list.collect {|f| f.filename}
|
||||
old_file_paths = current_site.file_list.collect {|f| f[:path]}
|
||||
|
||||
current_site.username = params[:name]
|
||||
|
||||
|
@ -629,7 +640,8 @@ def file_upload_response(error=nil)
|
|||
@error = error
|
||||
halt 200, erb(:'dashboard')
|
||||
else
|
||||
redirect '/dashboard'
|
||||
query_string = params[:dir] ? "?"+Rack::Utils.build_query(dir: params[:dir]) : ''
|
||||
redirect "/dashboard#{query_string}"
|
||||
end
|
||||
else
|
||||
halt http_error_code, error if error
|
||||
|
@ -637,6 +649,20 @@ def file_upload_response(error=nil)
|
|||
end
|
||||
end
|
||||
|
||||
post '/site/create_directory' do
|
||||
require_login
|
||||
|
||||
path = "#{params[:dir] || ''}/#{params[:name]}"
|
||||
|
||||
result = current_site.create_directory path
|
||||
|
||||
if result != true
|
||||
flash[:error] = e.message
|
||||
end
|
||||
|
||||
redirect "/dashboard?dir=#{Rack::Utils.escape params[:dir]}"
|
||||
end
|
||||
|
||||
post '/site_files/upload' do
|
||||
require_login
|
||||
@errors = []
|
||||
|
@ -647,12 +673,12 @@ post '/site_files/upload' do
|
|||
end
|
||||
|
||||
params[:files].each do |file|
|
||||
file[:filename] = "#{params[:dir]}/#{file[:filename]}" if params[:dir]
|
||||
if current_site.file_size_too_large? file[:tempfile].size
|
||||
file_upload_response "#{file[:filename]} is too large, upload cancelled."
|
||||
file_upload_response "#{params[:dir]}/#{file[:filename]} is too large, upload cancelled."
|
||||
end
|
||||
|
||||
if !Site.valid_file_type? file
|
||||
file_upload_response "#{file[:filename]}: file type (or content in file) is not allowed on Neocities, upload cancelled."
|
||||
file_upload_response "#{params[:dir]}/#{file[:filename]}: file type (or content in file) is not allowed on Neocities, upload cancelled."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -664,9 +690,8 @@ post '/site_files/upload' do
|
|||
|
||||
results = []
|
||||
params[:files].each do |file|
|
||||
results << current_site.store_file(Site.sanitize_filename(file[:filename]), file[:tempfile])
|
||||
results << current_site.store_file(file[:filename], file[:tempfile])
|
||||
end
|
||||
|
||||
current_site.increment_changed_count if results.include?(true)
|
||||
|
||||
file_upload_response
|
||||
|
@ -674,20 +699,18 @@ end
|
|||
|
||||
post '/site_files/delete' do
|
||||
require_login
|
||||
sanitized_filename = Site.sanitize_filename params[:filename]
|
||||
current_site.delete_file params[:filename]
|
||||
|
||||
current_site.delete_file(sanitized_filename)
|
||||
|
||||
flash[:success] = "Deleted file #{params[:filename]}."
|
||||
flash[:success] = "Deleted #{params[:filename]}."
|
||||
redirect '/dashboard'
|
||||
end
|
||||
|
||||
get '/site_files/:username.zip' do |username|
|
||||
require_login
|
||||
zipfile = current_site.files_zip
|
||||
zipfile_path = current_site.files_zip
|
||||
content_type 'application/octet-stream'
|
||||
attachment "#{current_site.username}.zip"
|
||||
zipfile
|
||||
attachment "neocities-#{current_site.username}.zip"
|
||||
send_file zipfile_path
|
||||
end
|
||||
|
||||
get '/site_files/download/:filename' do |filename|
|
||||
|
@ -697,10 +720,11 @@ get '/site_files/download/:filename' do |filename|
|
|||
current_site.get_file filename
|
||||
end
|
||||
|
||||
get '/site_files/text_editor/:filename' do |filename|
|
||||
get %r{\/site_files\/text_editor\/(.+)} do
|
||||
require_login
|
||||
@filename = params[:captures].first
|
||||
begin
|
||||
@file_data = current_site.get_file filename
|
||||
@file_data = current_site.get_file @filename
|
||||
rescue Errno::ENOENT
|
||||
flash[:error] = 'We could not find the requested file.'
|
||||
redirect '/dashboard'
|
||||
|
@ -708,8 +732,9 @@ get '/site_files/text_editor/:filename' do |filename|
|
|||
erb :'site_files/text_editor'
|
||||
end
|
||||
|
||||
post '/site_files/save/:filename' do |filename|
|
||||
post %r{\/site_files\/save\/(.+)} do
|
||||
require_login_ajax
|
||||
filename = params[:captures].first
|
||||
|
||||
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.'
|
||||
end
|
||||
|
||||
sanitized_filename = Site.sanitize_filename filename
|
||||
|
||||
current_site.store_file sanitized_filename, tempfile
|
||||
current_site.store_file filename, tempfile
|
||||
|
||||
'ok'
|
||||
end
|
||||
|
@ -937,14 +960,11 @@ end
|
|||
|
||||
post '/api/upload' do
|
||||
require_api_credentials
|
||||
|
||||
files = []
|
||||
|
||||
params.each do |k,v|
|
||||
next unless v.is_a?(Hash) && v[:tempfile]
|
||||
filename = k.to_s
|
||||
api_error(400, 'bad_filename', "#{filename} is not a valid filename, files not uploaded") unless Site.valid_filename? filename
|
||||
files << {filename: filename, tempfile: v[:tempfile]}
|
||||
path = k.to_s
|
||||
files << {filename: k || v[:filename], tempfile: v[:tempfile]}
|
||||
end
|
||||
|
||||
api_error 400, 'missing_files', 'you must provide files to upload' if files.empty?
|
||||
|
@ -959,6 +979,10 @@ post '/api/upload' do
|
|||
if !Site.valid_file_type?(file)
|
||||
api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type (or contains not allowed content), files have not been uploaded"
|
||||
end
|
||||
|
||||
if File.directory? file[:filename]
|
||||
api_error 400, 'directory_exists', 'this name is being used by a directory, cannot continue'
|
||||
end
|
||||
end
|
||||
|
||||
results = []
|
||||
|
@ -976,26 +1000,25 @@ post '/api/delete' do
|
|||
|
||||
api_error 400, 'missing_filenames', 'you must provide files to delete' if params[:filenames].nil? || params[:filenames].empty?
|
||||
|
||||
filenames = []
|
||||
|
||||
params[:filenames].each do |filename|
|
||||
unless filename.is_a?(String) && Site.valid_filename?(filename)
|
||||
api_error 400, 'bad_filename', "#{filename} is not a valid filename, canceled deleting"
|
||||
paths = []
|
||||
params[:filenames].each do |path|
|
||||
unless path.is_a?(String)
|
||||
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
||||
end
|
||||
|
||||
if !current_site.file_exists?(filename)
|
||||
api_error 400, 'missing_files', "#{filename} was not found on your site, canceled deleting"
|
||||
if !current_site.file_exists?(path)
|
||||
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
||||
end
|
||||
|
||||
if filename == 'index.html'
|
||||
if path == 'index.html'
|
||||
api_error 400, 'cannot_delete_index', 'you cannot delete your index.html file, canceled deleting'
|
||||
end
|
||||
|
||||
filenames << filename
|
||||
paths << path
|
||||
end
|
||||
|
||||
filenames.each do |filename|
|
||||
current_site.delete_file(filename)
|
||||
paths.each do |path|
|
||||
current_site.delete_file(path)
|
||||
end
|
||||
|
||||
api_success 'file(s) have been deleted'
|
||||
|
|
58
config.ru
58
config.ru
|
@ -1,8 +1,64 @@
|
|||
require 'rubygems'
|
||||
require './app.rb'
|
||||
require 'sidekiq/web'
|
||||
|
||||
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
|
||||
use Rack::Auth::Basic, "Protected Area" do |username, password|
|
||||
raise 'missing sidekiq auth' unless $config['sidekiq_user'] && $config['sidekiq_pass']
|
||||
|
@ -10,4 +66,4 @@ map '/sidekiq' do
|
|||
end
|
||||
|
||||
run Sidekiq::Web
|
||||
end
|
||||
end
|
|
@ -7,7 +7,6 @@ Encoding.default_external = 'UTF-8'
|
|||
require 'yaml'
|
||||
require 'json'
|
||||
require 'logger'
|
||||
require 'zip'
|
||||
|
||||
Bundler.require
|
||||
Bundler.require :development if ENV['RACK_ENV'] == 'development'
|
||||
|
@ -28,8 +27,6 @@ end
|
|||
DB = Sequel.connect $config['database'], sslmode: 'disable', max_connections: $config['database_pool']
|
||||
DB.extension :pagination
|
||||
|
||||
Dir.glob('workers/*.rb').each {|w| require File.join(DIR_ROOT, "/#{w}") }
|
||||
|
||||
if defined?(Pry)
|
||||
Pry.commands.alias_command 'c', 'continue'
|
||||
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'
|
||||
end
|
||||
|
||||
require File.join(DIR_ROOT, 'workers', 'thumbnail_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', 'thumbnail_worker.rb')
|
||||
#require File.join(DIR_ROOT, 'workers', 'screenshot_worker.rb')
|
||||
#require File.join(DIR_ROOT, 'workers', 'email_worker.rb')
|
||||
|
||||
Sequel.datetime_class = Time
|
||||
Sequel.extension :core_extensions
|
||||
|
@ -77,6 +74,9 @@ Sequel::Migrator.apply DB, './migrations'
|
|||
Stripe.api_key = $config['stripe_api_key']
|
||||
|
||||
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'
|
||||
|
||||
if ENV['RACK_ENV'] == 'development'
|
||||
|
@ -100,3 +100,16 @@ Mail.defaults do
|
|||
end
|
||||
|
||||
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 'rss'
|
||||
require 'nokogiri'
|
||||
require 'pathname'
|
||||
|
||||
class Site < Sequel::Model
|
||||
include Sequel::ParanoidDelete
|
||||
|
@ -23,6 +24,7 @@ class Site < Sequel::Model
|
|||
image/x-icon
|
||||
application/pdf
|
||||
application/pgp-keys
|
||||
application/pgp
|
||||
text/xml
|
||||
application/xml
|
||||
audio/midi
|
||||
|
@ -49,8 +51,8 @@ class Site < Sequel::Model
|
|||
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'))
|
||||
THUMBNAILS_ROOT = File.join(PUBLIC_ROOT, (ENV['RACK_ENV'] == 'test' ? 'site_thumbnails_test' : 'site_thumbnails'))
|
||||
SCREENSHOTS_URL_ROOT = '/site_screenshots'
|
||||
THUMBNAILS_URL_ROOT = '/site_thumbnails'
|
||||
SCREENSHOTS_URL_ROOT = ENV['RACK_ENV'] == 'test' ? '/site_screenshots_test' : '/site_screenshots'
|
||||
THUMBNAILS_URL_ROOT = ENV['RACK_ENV'] == 'test' ? '/site_thumbnails_test' : '/site_thumbnails'
|
||||
IMAGE_REGEX = /jpg|jpeg|png|bmp|gif/
|
||||
LOSSLESS_IMAGE_REGEX = /png|bmp|gif/
|
||||
LOSSY_IMAGE_REGEX = /jpg|jpeg/
|
||||
|
@ -70,6 +72,8 @@ class Site < Sequel::Model
|
|||
|
||||
EMAIL_SANITY_REGEX = /.+@.+\..+/i
|
||||
|
||||
EDITABLE_FILE_EXT = /html|htm|txt|js|css|md/i
|
||||
|
||||
BANNED_TIME = 2592000 # 30 days in seconds
|
||||
|
||||
TITLE_MAX = 100
|
||||
|
@ -197,16 +201,16 @@ class Site < Sequel::Model
|
|||
FileUtils.mkdir_p files_path
|
||||
|
||||
%w{index not_found}.each do |name|
|
||||
File.write file_path("#{name}.html"), render_template("#{name}.erb")
|
||||
purge_cache "#{name}.html"
|
||||
File.write files_path("#{name}.html"), render_template("#{name}.erb")
|
||||
purge_cache "/#{name}.html"
|
||||
ScreenshotWorker.perform_async values[:username], "#{name}.html"
|
||||
end
|
||||
|
||||
FileUtils.cp template_file_path('cat.png'), file_path('cat.png')
|
||||
FileUtils.cp template_file_path('cat.png'), files_path('cat.png')
|
||||
end
|
||||
|
||||
def get_file(filename)
|
||||
File.read file_path(filename)
|
||||
def get_file(path)
|
||||
File.read files_path(path)
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
|
@ -252,8 +256,8 @@ class Site < Sequel::Model
|
|||
FileUtils.mv files_path, File.join(PUBLIC_ROOT, 'banned_sites', username)
|
||||
}
|
||||
|
||||
site_files.file_list.collect {|f| f.filename}.each do |f|
|
||||
purge_cache f
|
||||
file_list.each do |path|
|
||||
purge_cache path
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -290,15 +294,6 @@ class Site < Sequel::Model
|
|||
!@blockings.select {|b| b.site_id == site.id}.empty?
|
||||
end
|
||||
|
||||
def self.valid_filename?(filename)
|
||||
return false if sanitize_filename(filename) != filename
|
||||
true
|
||||
end
|
||||
|
||||
def self.sanitize_filename(filename)
|
||||
filename.gsub(/[^a-zA-Z0-9_\-.]/, '')
|
||||
end
|
||||
|
||||
def self.valid_username?(username)
|
||||
!username.empty? && username.match(/^[a-zA-Z0-9_\-]+$/i)
|
||||
end
|
||||
|
@ -331,19 +326,24 @@ class Site < Sequel::Model
|
|||
true
|
||||
end
|
||||
|
||||
def purge_cache(filename)
|
||||
payload = {site: username, path: filename}
|
||||
def purge_cache(path)
|
||||
relative_path = path.gsub(base_files_path, '')
|
||||
payload = {site: username, path: relative_path}
|
||||
payload[:domain] = domain if !domain.empty?
|
||||
PurgeCacheWorker.perform_async payload
|
||||
end
|
||||
|
||||
def store_file(filename, uploaded)
|
||||
if File.exist?(file_path(filename)) &&
|
||||
Digest::SHA2.file(file_path(filename)).digest == Digest::SHA2.file(uploaded.path).digest
|
||||
def store_file(path, uploaded)
|
||||
relative_path = scrubbed_path path
|
||||
path = files_path path
|
||||
|
||||
if File.exist?(path) &&
|
||||
Digest::SHA2.file(path).digest == Digest::SHA2.file(uploaded.path).digest
|
||||
return false
|
||||
end
|
||||
|
||||
if filename == 'index.html'
|
||||
pathname = Pathname(path)
|
||||
if pathname.basename.to_s == 'index.html'
|
||||
new_title = Nokogiri::HTML(File.read(uploaded.path)).css('title').first.text
|
||||
|
||||
if new_title.length < TITLE_MAX
|
||||
|
@ -352,20 +352,26 @@ class Site < Sequel::Model
|
|||
end
|
||||
end
|
||||
|
||||
FileUtils.mv uploaded.path, file_path(filename)
|
||||
File.chmod(0640, file_path(filename))
|
||||
dirname = pathname.dirname.to_s
|
||||
|
||||
purge_cache filename
|
||||
|
||||
ext = File.extname(filename).gsub(/^./, '')
|
||||
|
||||
if ext.match HTML_REGEX
|
||||
ScreenshotWorker.perform_async values[:username], filename
|
||||
elsif ext.match IMAGE_REGEX
|
||||
ThumbnailWorker.perform_async values[:username], filename
|
||||
if !File.exists? dirname
|
||||
FileUtils.mkdir_p dirname
|
||||
end
|
||||
|
||||
SiteChange.record self, filename
|
||||
FileUtils.mv uploaded.path, path
|
||||
File.chmod 0640, path
|
||||
|
||||
purge_cache path
|
||||
|
||||
ext = File.extname(path).gsub(/^./, '')
|
||||
|
||||
if ext.match HTML_REGEX
|
||||
ScreenshotWorker.perform_async values[:username], 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
|
||||
self.site_changed = true
|
||||
|
@ -375,6 +381,20 @@ class Site < Sequel::Model
|
|||
true
|
||||
end
|
||||
|
||||
def is_directory?(path)
|
||||
File.directory? files_path(path)
|
||||
end
|
||||
|
||||
def create_directory(path)
|
||||
relative_path = files_path path
|
||||
if Dir.exists?(relative_path) || File.exist?(relative_path)
|
||||
return 'Directory (or file) already exists.'
|
||||
end
|
||||
|
||||
FileUtils.mkdir_p relative_path
|
||||
true
|
||||
end
|
||||
|
||||
def increment_changed_count
|
||||
self.changed_count += 1
|
||||
self.updated_at = Time.now
|
||||
|
@ -382,49 +402,61 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
def files_zip
|
||||
file_path = "/tmp/neocities-site-#{username}.zip"
|
||||
zip_name = "neocities-#{username}"
|
||||
|
||||
Zip::File.open(file_path, Zip::File::CREATE) do |zipfile|
|
||||
file_list.collect {|f| f.filename}.each do |filename|
|
||||
zipfile.add filename, file_path(filename)
|
||||
tmpfile = Tempfile.new 'neocities-site-zip'
|
||||
tmpfile.close
|
||||
|
||||
Zip::Archive.open(tmpfile.path, Zip::CREATE) do |ar|
|
||||
ar.add_dir(zip_name)
|
||||
|
||||
Dir.glob("#{base_files_path}/**/*").each do |path|
|
||||
relative_path = path.gsub(base_files_path+'/', '')
|
||||
|
||||
if File.directory?(path)
|
||||
ar.add_dir(zip_name+'/'+relative_path)
|
||||
else
|
||||
ar.add_file(zip_name+'/'+relative_path, path) # add_file(<entry name>, <source path>)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO Don't dump the zipfile into memory
|
||||
zipfile = File.read file_path
|
||||
File.delete file_path
|
||||
zipfile
|
||||
tmpfile.path
|
||||
end
|
||||
|
||||
def delete_file(filename)
|
||||
def delete_file(path)
|
||||
begin
|
||||
FileUtils.rm file_path(filename)
|
||||
FileUtils.rm files_path(path)
|
||||
rescue Errno::EISDIR
|
||||
FileUtils.remove_dir files_path(path), true
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
|
||||
purge_cache filename
|
||||
purge_cache path
|
||||
|
||||
ext = File.extname(filename).gsub(/^./, '')
|
||||
ext = File.extname(path).gsub(/^./, '')
|
||||
|
||||
screenshots_delete(filename) if ext.match HTML_REGEX
|
||||
thumbnails_delete(filename) if ext.match IMAGE_REGEX
|
||||
screenshots_delete(path) if ext.match HTML_REGEX
|
||||
thumbnails_delete(path) if ext.match IMAGE_REGEX
|
||||
|
||||
SiteChangeFile.filter(site_id: self.id, filename: filename).delete
|
||||
path = path[1..path.length] if path[0] == '/'
|
||||
|
||||
SiteChangeFile.filter(site_id: self.id, filename: path).delete
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def move_files_from(oldusername)
|
||||
FileUtils.mv files_path(oldusername), files_path
|
||||
FileUtils.mv base_files_path(oldusername), base_files_path
|
||||
end
|
||||
|
||||
def install_new_html_file(name)
|
||||
File.write file_path(name), render_template('index.erb')
|
||||
purge_cache name
|
||||
def install_new_html_file(path)
|
||||
File.write files_path(path), render_template('index.erb')
|
||||
purge_cache path
|
||||
end
|
||||
|
||||
def file_exists?(filename)
|
||||
File.exist? file_path(filename)
|
||||
def file_exists?(path)
|
||||
File.exist? files_path(path)
|
||||
end
|
||||
|
||||
def after_save
|
||||
|
@ -453,7 +485,7 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
# def after_destroy
|
||||
# FileUtils.rm_rf file_path
|
||||
# FileUtils.rm_rf files_path
|
||||
# super
|
||||
# end
|
||||
|
||||
|
@ -568,16 +600,48 @@ class Site < Sequel::Model
|
|||
File.join TEMPLATE_ROOT, name
|
||||
end
|
||||
|
||||
def files_path(name=nil)
|
||||
File.join SITE_FILES_ROOT, (name || username)
|
||||
def base_files_path(name=username)
|
||||
raise 'username missing' if name.nil? || name.empty?
|
||||
File.join SITE_FILES_ROOT, name
|
||||
end
|
||||
|
||||
def file_path(filename)
|
||||
File.join files_path, filename
|
||||
# https://practicingruby.com/articles/implementing-an-http-file-server?u=dc2ab0f9bb
|
||||
def scrubbed_path(path='')
|
||||
path ||= ''
|
||||
clean = []
|
||||
|
||||
parts = path.split '/'
|
||||
|
||||
parts.each do |part|
|
||||
next if part.empty? || part == '.'
|
||||
clean << part if part != '..'
|
||||
end
|
||||
|
||||
clean.join '/'
|
||||
end
|
||||
|
||||
def file_list
|
||||
Dir.glob(File.join(files_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename}
|
||||
def files_path(path='')
|
||||
File.join base_files_path, scrubbed_path(path)
|
||||
end
|
||||
|
||||
def file_list(path='')
|
||||
list = Dir.glob(File.join(files_path(path), '*')).collect do |file_path|
|
||||
file = {
|
||||
path: file_path.gsub(base_files_path, ''),
|
||||
name: File.basename(file_path),
|
||||
ext: File.extname(file_path).gsub('.', ''),
|
||||
is_directory: File.directory?(file_path),
|
||||
is_root_index: file_path == "#{base_files_path}/index.html"
|
||||
}
|
||||
|
||||
file[:is_html] = !(file[:ext].match HTML_REGEX).nil?
|
||||
file[:is_image] = !(file[:ext].match IMAGE_REGEX).nil?
|
||||
file[:is_editable] = !(file[:ext].match EDITABLE_FILE_EXT).nil?
|
||||
file
|
||||
end
|
||||
|
||||
list.select {|f| f[:is_directory]}.sort_by {|f| f[:name]} +
|
||||
list.select {|f| f[:is_directory] == false}.sort_by{|f| f[:name]}
|
||||
end
|
||||
|
||||
def file_size_too_large?(size_in_bytes)
|
||||
|
@ -658,19 +722,19 @@ class Site < Sequel::Model
|
|||
values[:hits].to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
|
||||
end
|
||||
|
||||
def screenshots_delete(filename)
|
||||
def screenshots_delete(path)
|
||||
SCREENSHOT_RESOLUTIONS.each do |res|
|
||||
begin
|
||||
FileUtils.rm screenshot_path(filename, res)
|
||||
FileUtils.rm screenshot_path(path, res)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def thumbnails_delete(filename)
|
||||
def thumbnails_delete(path)
|
||||
THUMBNAIL_RESOLUTIONS.each do |res|
|
||||
begin
|
||||
FileUtils.rm thumbnail_path(filename, res)
|
||||
FileUtils.rm thumbnail_path(path, res)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
end
|
||||
|
@ -680,34 +744,34 @@ class Site < Sequel::Model
|
|||
Site.where(tags: tags).limit(limit, offset).order(:updated_at.desc).all
|
||||
end
|
||||
|
||||
def screenshot_path(filename, resolution)
|
||||
File.join(SCREENSHOTS_ROOT, values[:username], "#{filename}.#{resolution}.jpg")
|
||||
def screenshot_path(path, resolution)
|
||||
File.join(SCREENSHOTS_ROOT, values[:username], "#{path}.#{resolution}.jpg")
|
||||
end
|
||||
|
||||
def screenshot_exists?(filename, resolution)
|
||||
File.exist? File.join(SCREENSHOTS_ROOT, values[:username], "#{filename}.#{resolution}.jpg")
|
||||
def screenshot_exists?(path, resolution)
|
||||
File.exist? File.join(SCREENSHOTS_ROOT, values[:username], "#{path}.#{resolution}.jpg")
|
||||
end
|
||||
|
||||
def screenshot_url(filename, resolution)
|
||||
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.jpg"
|
||||
def screenshot_url(path, resolution)
|
||||
"#{SCREENSHOTS_URL_ROOT}/#{values[:username]}/#{path}.#{resolution}.jpg"
|
||||
end
|
||||
|
||||
def thumbnail_path(filename, resolution)
|
||||
ext = File.extname(filename).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
File.join THUMBNAILS_ROOT, values[:username], "#{filename}.#{resolution}.#{ext}"
|
||||
def thumbnail_path(path, resolution)
|
||||
ext = File.extname(path).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
File.join THUMBNAILS_ROOT, values[:username], "#{path}.#{resolution}.#{ext}"
|
||||
end
|
||||
|
||||
def thumbnail_exists?(filename, resolution)
|
||||
File.exist? thumbnail_path(filename, resolution)
|
||||
def thumbnail_exists?(path, resolution)
|
||||
File.exist? thumbnail_path(path, resolution)
|
||||
end
|
||||
|
||||
def thumbnail_delete(filename, resolution)
|
||||
File.rm thumbnail_path(filename, resolution)
|
||||
def thumbnail_delete(path, resolution)
|
||||
File.rm thumbnail_path(path, resolution)
|
||||
end
|
||||
|
||||
def thumbnail_url(filename, resolution)
|
||||
ext = File.extname(filename).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
"#{THUMBNAILS_URL_ROOT}/#{values[:username]}/#{filename}.#{resolution}.#{ext}"
|
||||
def thumbnail_url(path, resolution)
|
||||
ext = File.extname(path).gsub('.', '').match(LOSSY_IMAGE_REGEX) ? 'jpg' : 'png'
|
||||
"#{THUMBNAILS_URL_ROOT}/#{values[:username]}/#{path}.#{resolution}.#{ext}"
|
||||
end
|
||||
|
||||
def to_rss
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
class SiteFile
|
||||
attr_reader :filename, :ext
|
||||
|
||||
def initialize(filename)
|
||||
@filename = filename
|
||||
@ext = File.extname(@filename).sub(/^./, '')
|
||||
end
|
||||
end
|
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 {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
font-size: 8px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
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'
|
||||
|
||||
Capybara.app = Sinatra::Application
|
||||
|
||||
def teardown
|
||||
Capybara.reset_sessions!
|
||||
Capybara.use_default_driver
|
||||
end
|
||||
|
||||
describe 'index' do
|
||||
include Capybara::DSL
|
||||
it 'goes to signup' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
page.must_have_content('Create a New Website')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'change username' do
|
||||
include Capybara::DSL
|
||||
|
||||
def visit_signup
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
end
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for(:site)
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
fill_in 'email', with: @site[:email]
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
visit_signup
|
||||
end
|
||||
|
||||
it 'does not allow bad usernames' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
fill_in_valid
|
||||
click_button 'Create Home Page'
|
||||
visit '/settings'
|
||||
fill_in 'name', with: ''
|
||||
click_button 'Change Name'
|
||||
fill_in 'name', with: '../hack'
|
||||
click_button 'Change Name'
|
||||
fill_in 'name', with: 'derp../hack'
|
||||
click_button 'Change Name'
|
||||
## TODO fix this without screwing up legacy sites
|
||||
#fill_in 'name', with: '-'
|
||||
#click_button 'Change Name'
|
||||
page.must_have_content /valid.+name.+required/i
|
||||
Site[username: @site[:username]].wont_equal nil
|
||||
Site[username: ''].must_equal nil
|
||||
end
|
||||
end
|
||||
require_relative './environment.rb'
|
||||
|
||||
describe 'signup' do
|
||||
include Capybara::DSL
|
||||
|
@ -215,56 +158,4 @@ describe 'signup' do
|
|||
page.must_have_content 'Your Feed'
|
||||
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'signin' do
|
||||
include Capybara::DSL
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for :site
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
end
|
||||
|
||||
def fill_in_valid_signup
|
||||
fill_in_valid
|
||||
fill_in 'email', with: @site[:email]
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
end
|
||||
|
||||
it 'fails for invalid login' do
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
page.must_have_content 'Welcome Back'
|
||||
fill_in_valid
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Invalid login'
|
||||
end
|
||||
|
||||
it 'fails for missing login' do
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
auth = {username: SecureRandom.hex, password: Faker::Internet.password}
|
||||
fill_in 'username', with: auth[:username]
|
||||
fill_in 'password', with: auth[:password]
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Invalid login'
|
||||
end
|
||||
|
||||
it 'logs in with proper credentials' do
|
||||
visit '/'
|
||||
click_button 'Create My Website'
|
||||
fill_in_valid_signup
|
||||
click_button 'Create Home Page'
|
||||
Capybara.reset_sessions!
|
||||
visit '/'
|
||||
click_link 'Sign In'
|
||||
fill_in 'username', with: @site[:username]
|
||||
fill_in 'password', with: @site[:password]
|
||||
click_button 'Sign In'
|
||||
page.must_have_content 'Your Feed'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,17 +84,17 @@ describe 'api delete' do
|
|||
res[:error_type].must_equal 'cannot_delete_index'
|
||||
end
|
||||
|
||||
it 'fails with bad filename' do
|
||||
it 'succeeds with weird filenames' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@site.store_file 't$st.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
post '/api/delete', filenames: ['t$st.jpg']
|
||||
res[:error_type].must_equal 'bad_filename'
|
||||
res[:result].must_equal 'success'
|
||||
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/delete', filenames: ['./config.yml']
|
||||
res[:error_type].must_equal 'bad_filename'
|
||||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails with missing files' do
|
||||
|
@ -137,13 +137,59 @@ describe 'api upload' do
|
|||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails for invalid filenames' do
|
||||
it 'resists directory traversal attack' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'../lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'bad_filename'
|
||||
res[:result].must_equal 'success'
|
||||
File.exist?(File.join(Site::SITE_FILES_ROOT, @site.username, 'lol.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'scrubs root path slash' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'/lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:result].must_equal 'success'
|
||||
File.exist?(File.join(Site::SITE_FILES_ROOT, @site.username, 'lol.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'fails for missing file name' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'/' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'invalid_file_type'
|
||||
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails for file with no extension' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'derpie' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'invalid_file_type'
|
||||
end
|
||||
|
||||
it 'creates path for file uploads' do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'derpie/derpingtons/lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:result].must_equal 'success'
|
||||
File.exist?(@site.files_path('derpie/derpingtons/lol.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'fails for invalid files' do
|
||||
|
@ -180,7 +226,7 @@ describe 'api upload' do
|
|||
end
|
||||
|
||||
def site_file_exists?(file)
|
||||
File.exist?(@site.file_path('test.jpg'))
|
||||
File.exist?(@site.files_path('test.jpg'))
|
||||
end
|
||||
|
||||
def res
|
||||
|
|
|
@ -9,18 +9,26 @@ end
|
|||
|
||||
SimpleCov.command_name 'minitest'
|
||||
|
||||
require 'rack_session_access'
|
||||
require './environment'
|
||||
require 'webmock'
|
||||
include WebMock::API
|
||||
require './app'
|
||||
|
||||
Bundler.require :test
|
||||
|
||||
#require 'minitest/pride'
|
||||
require 'minitest/autorun'
|
||||
|
||||
require 'webmock'
|
||||
include WebMock::API
|
||||
require 'webmock/minitest'
|
||||
require 'sidekiq/testing'
|
||||
|
||||
Sinatra::Application.configure do |app|
|
||||
app.use RackSessionAccess::Middleware
|
||||
end
|
||||
|
||||
require 'capybara/poltergeist'
|
||||
require 'rack_session_access/capybara'
|
||||
|
||||
Site.bcrypt_cost = BCrypt::Engine::MIN_COST
|
||||
|
||||
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
|
||||
|
|
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>
|
||||
<% else %>
|
||||
<li>
|
||||
<a href="/dashboard" class="sign-In">Dashboard</a>
|
||||
<a href="/dashboard" class="sign-In">Edit Site</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/settings" class="sign-In">Settings</a>
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<%= File.extname(f).sub('.', '') %>
|
||||
</span>
|
||||
<% end %>
|
||||
<span class="title"><%= f %></span>
|
||||
<span class="title" title="<%= f %>"><%= f %></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -73,7 +73,10 @@
|
|||
</div>
|
||||
<div class="site-info">
|
||||
<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 style="float: right">
|
||||
<a href="/site/<%= site.username %>">
|
||||
|
|
|
@ -15,14 +15,6 @@
|
|||
display: none;
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
<form action="/site_files/upload" class="dropzone" id="uploads">
|
||||
<input name="csrf_token" type="hidden" value="#{csrf_token}">
|
||||
</form>
|
||||
|
||||
|
||||
<div id="upload_status" style="font-size: 16pt"></div>
|
||||
-->
|
||||
|
||||
<div class="header-Outro with-site-image">
|
||||
<div class="row content wide">
|
||||
|
@ -88,10 +80,28 @@
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<a href="/site_files/new_page" class="btn-Action new-Page"><span>New Page</span></a>
|
||||
<a href="" class="btn-Action new-Folder"><span>New Folder</span></a>
|
||||
<a href="#createDir" class="btn-Action new-Folder" data-toggle="modal"><span>New Folder</span></a>
|
||||
<a href="#" class="btn-Action upload" onclick="clickUploadFiles(); return false"><span>Upload</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -99,47 +109,49 @@
|
|||
<form action="/site_files/upload" class="dropzone" id="uploads">
|
||||
<div class="dz-message" style="display: none"></div>
|
||||
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||
<div class="upload-Boundary <%= current_site.file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||
<% current_site.file_list.each do |file| %>
|
||||
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||
<% @file_list.each do |file| %>
|
||||
<div class="file filehover">
|
||||
<% if file.ext.match(Site::HTML_REGEX) && current_site.screenshot_exists?(file.filename, '105x63') %>
|
||||
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '105x63') %>
|
||||
<div class="html-thumbnail html fileimagehover">
|
||||
<img src="<%= current_site.screenshot_url(file.filename, '105x63') %>">
|
||||
<img src="<%= current_site.screenshot_url(file[:path], '105x63') %>">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<% elsif file.ext.match(Site::IMAGE_REGEX) && current_site.thumbnail_exists?(file.filename, '105x63') %>
|
||||
<% elsif file[:is_image] && current_site.thumbnail_exists?(file[:path], '105x63') %>
|
||||
<div class="html-thumbnail image fileimagehover">
|
||||
<img src="<%= current_site.thumbnail_url(file.filename, '105x63') %>" style="">
|
||||
<img src="<%= current_site.thumbnail_url(file[:path], '105x63') %>" style="">
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<!-- if folder
|
||||
<% elsif file[:is_directory] %>
|
||||
<div class="html-thumbnail folder fileimagehover">
|
||||
<div class="folder-icon"></div>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
-->
|
||||
<% else %>
|
||||
<div class="html-thumbnail misc fileimagehover">
|
||||
<div class="misc-icon"><%= file.ext %></div>
|
||||
<div class="misc-icon"><%= file[:ext] %></div>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<a class="title">
|
||||
<% if file.filename.length > 14 %>
|
||||
<%= file.filename.slice(0..14) %>…
|
||||
<% if file[:name].length > 15 %>
|
||||
<%= file[:name].slice(0..14) %>…
|
||||
<% else %>
|
||||
<%= file.filename %>
|
||||
<%= file[:name] %>
|
||||
<% end %>
|
||||
</a>
|
||||
<div class="overlay">
|
||||
<% if file.ext.match(/html|htm|txt|js|css|md/) %>
|
||||
<a href="/site_files/text_editor/<%= file.filename %>"><i class="icon-edit" title="Edit"> Edit</i></a>
|
||||
<% if file[:is_editable] %>
|
||||
<a href="/site_files/text_editor<%= file[:path] %>"><i class="icon-edit" title="Edit"> Edit</i></a>
|
||||
<% end %>
|
||||
<% if file.filename != 'index.html' %>
|
||||
<a href="#" onclick="confirmFileDelete('<%= file.filename %>')"><i class="icon-trash" title="Delete"> Delete</i></a>
|
||||
<% if file[:is_directory] %>
|
||||
<a href="?dir=<%= Rack::Utils.escape file[:path] %>"><i class="icon-edit" title="Manage"> Manage</i></a>
|
||||
<% end %>
|
||||
<a class="link-overlay" href="http://<%= current_site.username %>.neocities.org/<%= file.filename %>" title="View <%= file.filename %>" target="_blank"></a>
|
||||
<% if !file[:is_root_index] %>
|
||||
<a href="#" onclick="confirmFileDelete('<%= file[:path] %>')"><i class="icon-trash" title="Delete"> Delete</i></a>
|
||||
<% end %>
|
||||
<a class="link-overlay" href="http://<%= current_site.username %>.neocities.org<%= file[:path] %>" title="View <%= file[:path] %>" target="_blank"></a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -158,7 +170,7 @@
|
|||
<h3 id="deleteConfirmModalLabel">Confirm deletion</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete the file <span id="deleteFileName"></span>. Are you sure?</p>
|
||||
<p>You are about to delete "<span id="deleteFileName"></span>". Are you sure?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
|
@ -174,9 +186,10 @@
|
|||
</div>
|
||||
</main>
|
||||
|
||||
<form id="uploadFilesButtonForm" method="POST" action="/site_files/upload" enctype=multipart/form-data style="display: none" onsubmit="showUploadProgress()">
|
||||
<form id="uploadFilesButtonForm" method="POST" action="/site_files/upload" enctype="multipart/form-data" style="display: none" onsubmit="showUploadProgress()">
|
||||
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||
<input name="from_button" type="hidden" value="true">
|
||||
<input name="dir" type="hidden" value="<%= @dir %>">
|
||||
<input id="uploadFiles" type="file" name="files[]" multiple onchange="$('#uploadFilesButtonForm').submit()">
|
||||
</form>
|
||||
|
||||
|
@ -224,7 +237,6 @@
|
|||
})
|
||||
|
||||
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
||||
console.log('OH HI')
|
||||
showUploadProgress()
|
||||
$('#progressBar').css('display', 'block')
|
||||
$('#uploadingProgress').css('width', progress+'%')
|
||||
|
@ -232,3 +244,24 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="modal hide fade" id="createDir" tabindex="-1" role="dialog" aria-labelledby="createDirLabel" aria-hidden="true">
|
||||
<form method="POST" action="/site/create_directory">
|
||||
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
|
||||
<input type="hidden" value="<%= @dir %>" name="dir">
|
||||
<div class="modal-header">
|
||||
<button class="close" type="button" data-dismiss="modal" aria-hidden="true">x</button>
|
||||
<h3 id="createDirLabel">Create Folder</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Folder name:
|
||||
</p>
|
||||
<input name="name" type="text" placeholder="images">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-Action">Create</button>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<div class="header-Outro">
|
||||
<div class="row content">
|
||||
<h1>Editing <%= params[:filename] %></h1>
|
||||
<h1>Editing <%= @filename %></h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -120,11 +120,14 @@
|
|||
|
||||
function saveTextFile(quit) {
|
||||
$.ajax({
|
||||
url: '/site_files/save/<%= params[:filename] %>?csrf_token=<%= csrf_token %>',
|
||||
url: '/site_files/save/<%= @filename %>?csrf_token=<%= csrf_token %>',
|
||||
data: editor.getValue(),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
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){
|
||||
if(response == 'ok') {
|
||||
if(quit === true) {
|
||||
|
|
|
@ -27,24 +27,25 @@ module Phantomjs
|
|||
end
|
||||
|
||||
class ScreenshotWorker
|
||||
SCREENSHOTS_PATH = File.join DIR_ROOT, 'public', 'site_screenshots'
|
||||
SCREENSHOTS_PATH = Site::SCREENSHOTS_ROOT
|
||||
include Sidekiq::Worker
|
||||
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.close
|
||||
screenshot_output_path = screenshot.path+'.png'
|
||||
|
||||
begin
|
||||
f = Screencap::Fetcher.new("http://#{username}.neocities.org/#{filename}")
|
||||
f = Screencap::Fetcher.new("http://#{username}.neocities.org#{path}")
|
||||
f.fetch(
|
||||
output: screenshot_output_path,
|
||||
width: 1280,
|
||||
height: 720
|
||||
)
|
||||
rescue Timeout::Error
|
||||
puts "#{username}/#{filename} is timing out, discontinuing"
|
||||
puts "#{username}/#{path} is timing out, discontinuing"
|
||||
site = Site[username: username]
|
||||
site.update is_crashing: true
|
||||
|
||||
|
@ -54,7 +55,7 @@ class ScreenshotWorker
|
|||
EmailWorker.perform_async({
|
||||
from: 'web@neocities.org',
|
||||
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 "+
|
||||
"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, "+
|
||||
|
@ -79,10 +80,12 @@ class ScreenshotWorker
|
|||
img = img_list.reverse.flatten_images
|
||||
|
||||
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|
|
||||
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
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
require 'RMagick'
|
||||
|
||||
class ThumbnailWorker
|
||||
THUMBNAILS_PATH = File.join DIR_ROOT, 'public', 'site_thumbnails'
|
||||
THUMBNAILS_PATH = Site::THUMBNAILS_ROOT
|
||||
include Sidekiq::Worker
|
||||
sidekiq_options queue: :thumbnails, retry: 3, backtrace: true
|
||||
|
||||
def perform(username, filename)
|
||||
def perform(username, path)
|
||||
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
|
||||
|
||||
user_thumbnails_path = File.join THUMBNAILS_PATH, username
|
||||
FileUtils.mkdir_p user_thumbnails_path
|
||||
FileUtils.mkdir_p File.join(user_thumbnails_path, File.dirname(path))
|
||||
|
||||
Site::THUMBNAIL_RESOLUTIONS.each do |res|
|
||||
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'
|
||||
|
||||
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
|
||||
}
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue