mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +02:00
fix for site tags referencing followed counts
This commit is contained in:
commit
d264672b51
114 changed files with 4538 additions and 1052 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -34,3 +34,4 @@ files/sslsites.zip
|
||||||
.vagrant
|
.vagrant
|
||||||
public/banned_sites
|
public/banned_sites
|
||||||
public/deleted_sites
|
public/deleted_sites
|
||||||
|
tests/stat_logs/*
|
||||||
|
|
6
Gemfile
6
Gemfile
|
@ -26,6 +26,10 @@ gem 'thread'
|
||||||
gem 'scrypt'
|
gem 'scrypt'
|
||||||
gem 'rack-cache'
|
gem 'rack-cache'
|
||||||
gem 'rest-client'
|
gem 'rest-client'
|
||||||
|
gem 'geoip'
|
||||||
|
gem 'io-extra', require: 'io/extra'
|
||||||
|
gem 'rye'
|
||||||
|
gem 'dnsruby'
|
||||||
|
|
||||||
platform :mri, :rbx do
|
platform :mri, :rbx do
|
||||||
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
||||||
|
@ -72,8 +76,10 @@ group :test do
|
||||||
gem 'rack_session_access', require: nil
|
gem 'rack_session_access', require: nil
|
||||||
gem 'webmock', require: nil
|
gem 'webmock', require: nil
|
||||||
gem 'stripe-ruby-mock', '~> 2.0.1', require: 'stripe_mock'
|
gem 'stripe-ruby-mock', '~> 2.0.1', require: 'stripe_mock'
|
||||||
|
gem 'timecop'
|
||||||
|
|
||||||
platform :mri, :rbx do
|
platform :mri, :rbx do
|
||||||
gem 'simplecov', require: nil
|
gem 'simplecov', require: nil
|
||||||
|
gem 'm'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
64
Gemfile.lock
64
Gemfile.lock
|
@ -9,6 +9,8 @@ GEM
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.3.7)
|
addressable (2.3.7)
|
||||||
ago (0.1.5)
|
ago (0.1.5)
|
||||||
|
annoy (0.5.6)
|
||||||
|
highline (>= 1.5.0)
|
||||||
ansi (1.4.3)
|
ansi (1.4.3)
|
||||||
autoparse (0.3.3)
|
autoparse (0.3.3)
|
||||||
addressable (>= 2.3.1)
|
addressable (>= 2.3.1)
|
||||||
|
@ -20,15 +22,15 @@ GEM
|
||||||
byebug (2.7.0)
|
byebug (2.7.0)
|
||||||
columnize (~> 0.3)
|
columnize (~> 0.3)
|
||||||
debugger-linecache (~> 1.2)
|
debugger-linecache (~> 1.2)
|
||||||
capybara (2.4.1)
|
capybara (2.4.4)
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rack-test (>= 0.5.4)
|
rack-test (>= 0.5.4)
|
||||||
xpath (~> 2.0)
|
xpath (~> 2.0)
|
||||||
capybara_minitest_spec (1.0.1)
|
capybara_minitest_spec (1.0.5)
|
||||||
capybara (>= 2)
|
capybara (>= 2)
|
||||||
minitest (>= 2)
|
minitest (>= 4)
|
||||||
celluloid (0.15.2)
|
celluloid (0.15.2)
|
||||||
timers (~> 1.1.0)
|
timers (~> 1.1.0)
|
||||||
climate_control (0.0.3)
|
climate_control (0.0.3)
|
||||||
|
@ -47,9 +49,11 @@ GEM
|
||||||
rack (>= 1.1.0)
|
rack (>= 1.1.0)
|
||||||
uuidtools (~> 2.1.1)
|
uuidtools (~> 2.1.1)
|
||||||
debugger-linecache (1.2.0)
|
debugger-linecache (1.2.0)
|
||||||
|
dnsruby (1.58.0)
|
||||||
docile (1.1.3)
|
docile (1.1.3)
|
||||||
domain_name (0.5.23)
|
domain_name (0.5.23)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
|
drydock (0.6.9)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
extlib (0.9.16)
|
extlib (0.9.16)
|
||||||
fabrication (2.11.0)
|
fabrication (2.11.0)
|
||||||
|
@ -62,6 +66,7 @@ GEM
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
rake
|
rake
|
||||||
filesize (0.0.3)
|
filesize (0.0.3)
|
||||||
|
geoip (1.5.0)
|
||||||
google-api-client (0.7.1)
|
google-api-client (0.7.1)
|
||||||
addressable (>= 2.3.2)
|
addressable (>= 2.3.2)
|
||||||
autoparse (>= 0.3.3)
|
autoparse (>= 0.3.3)
|
||||||
|
@ -74,10 +79,12 @@ GEM
|
||||||
signet (>= 0.5.0)
|
signet (>= 0.5.0)
|
||||||
uuidtools (>= 2.1.0)
|
uuidtools (>= 2.1.0)
|
||||||
hashie (2.0.5)
|
hashie (2.0.5)
|
||||||
|
highline (1.7.2)
|
||||||
hiredis (0.5.0)
|
hiredis (0.5.0)
|
||||||
http-cookie (1.0.2)
|
http-cookie (1.0.2)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
i18n (0.6.9)
|
i18n (0.6.9)
|
||||||
|
io-extra (1.2.8)
|
||||||
jimson-temp (0.9.5)
|
jimson-temp (0.9.5)
|
||||||
blankslate (>= 3.1.2)
|
blankslate (>= 3.1.2)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
|
@ -89,6 +96,9 @@ GEM
|
||||||
kgio (2.9.2)
|
kgio (2.9.2)
|
||||||
launchy (2.4.2)
|
launchy (2.4.2)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
|
m (1.3.4)
|
||||||
|
method_source (>= 0.6.7)
|
||||||
|
rake (>= 0.9.2.2)
|
||||||
magic (0.2.6)
|
magic (0.2.6)
|
||||||
ffi (>= 0.6.3)
|
ffi (>= 0.6.3)
|
||||||
mail (2.5.4)
|
mail (2.5.4)
|
||||||
|
@ -97,8 +107,8 @@ GEM
|
||||||
metaclass (0.0.4)
|
metaclass (0.0.4)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (1.25.1)
|
mime-types (1.25.1)
|
||||||
mini_portile (0.6.0)
|
mini_portile (0.6.2)
|
||||||
minitest (5.3.1)
|
minitest (5.6.1)
|
||||||
minitest-reporters (1.0.2)
|
minitest-reporters (1.0.2)
|
||||||
ansi
|
ansi
|
||||||
builder
|
builder
|
||||||
|
@ -106,14 +116,17 @@ GEM
|
||||||
powerbar
|
powerbar
|
||||||
mocha (1.0.0)
|
mocha (1.0.0)
|
||||||
metaclass (~> 0.0.1)
|
metaclass (~> 0.0.1)
|
||||||
multi_json (1.10.1)
|
multi_json (1.11.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
|
net-scp (1.2.1)
|
||||||
|
net-ssh (>= 2.6.5)
|
||||||
|
net-ssh (2.9.2)
|
||||||
netrc (0.10.3)
|
netrc (0.10.3)
|
||||||
nokogiri (1.6.3.1)
|
nokogiri (1.6.6.2)
|
||||||
mini_portile (= 0.6.0)
|
mini_portile (~> 0.6.0)
|
||||||
pg (0.17.1)
|
pg (0.17.1)
|
||||||
phantomjs (1.9.7.1)
|
phantomjs (1.9.7.1)
|
||||||
poltergeist (1.5.1)
|
poltergeist (1.6.0)
|
||||||
capybara (~> 2.1)
|
capybara (~> 2.1)
|
||||||
cliver (~> 0.3.1)
|
cliver (~> 0.3.1)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
|
@ -131,14 +144,14 @@ GEM
|
||||||
pry (~> 0.9.12)
|
pry (~> 0.9.12)
|
||||||
puma (2.8.1)
|
puma (2.8.1)
|
||||||
rack (>= 1.1, < 2.0)
|
rack (>= 1.1, < 2.0)
|
||||||
rack (1.5.2)
|
rack (1.6.0)
|
||||||
rack-cache (1.2)
|
rack-cache (1.2)
|
||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
rack-protection (1.5.2)
|
rack-protection (1.5.2)
|
||||||
rack
|
rack
|
||||||
rack-recaptcha (0.6.6)
|
rack-recaptcha (0.6.6)
|
||||||
json
|
json
|
||||||
rack-test (0.6.2)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rack_session_access (0.1.1)
|
rack_session_access (0.1.1)
|
||||||
builder (>= 2.0.0)
|
builder (>= 2.0.0)
|
||||||
|
@ -157,7 +170,14 @@ GEM
|
||||||
mime-types (>= 1.16, < 3.0)
|
mime-types (>= 1.16, < 3.0)
|
||||||
netrc (~> 0.7)
|
netrc (~> 0.7)
|
||||||
retriable (1.4.1)
|
retriable (1.4.1)
|
||||||
rmagick (2.13.3)
|
rmagick (2.15.0)
|
||||||
|
rye (0.9.13)
|
||||||
|
annoy
|
||||||
|
docile (>= 1.0.1)
|
||||||
|
highline (>= 1.5.1)
|
||||||
|
net-scp (>= 1.0.2)
|
||||||
|
net-ssh (>= 2.0.13)
|
||||||
|
sysinfo (>= 0.8.1)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.4)
|
||||||
sass (3.3.8)
|
sass (3.3.8)
|
||||||
screencap (0.1.1)
|
screencap (0.1.1)
|
||||||
|
@ -195,7 +215,8 @@ GEM
|
||||||
sinatra (>= 1.0.0)
|
sinatra (>= 1.0.0)
|
||||||
sinatra-xsendfile (0.4.2)
|
sinatra-xsendfile (0.4.2)
|
||||||
sinatra (>= 0.9.1)
|
sinatra (>= 0.9.1)
|
||||||
slop (3.5.0)
|
slop (3.6.0)
|
||||||
|
storable (0.8.9)
|
||||||
stripe (1.15.0)
|
stripe (1.15.0)
|
||||||
json (~> 1.8.1)
|
json (~> 1.8.1)
|
||||||
mime-types (>= 1.25, < 3.0)
|
mime-types (>= 1.25, < 3.0)
|
||||||
|
@ -204,9 +225,13 @@ GEM
|
||||||
dante (>= 0.2.0)
|
dante (>= 0.2.0)
|
||||||
jimson-temp
|
jimson-temp
|
||||||
stripe (>= 1.15.0)
|
stripe (>= 1.15.0)
|
||||||
|
sysinfo (0.8.1)
|
||||||
|
drydock
|
||||||
|
storable
|
||||||
thread (0.1.4)
|
thread (0.1.4)
|
||||||
thread_safe (0.3.4)
|
thread_safe (0.3.4)
|
||||||
tilt (1.4.1)
|
tilt (1.4.1)
|
||||||
|
timecop (0.7.4)
|
||||||
timers (1.1.0)
|
timers (1.1.0)
|
||||||
treetop (1.4.15)
|
treetop (1.4.15)
|
||||||
polyglot
|
polyglot
|
||||||
|
@ -224,7 +249,9 @@ GEM
|
||||||
webmock (1.17.4)
|
webmock (1.17.4)
|
||||||
addressable (>= 2.2.7)
|
addressable (>= 2.2.7)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
websocket-driver (0.3.4)
|
websocket-driver (0.5.4)
|
||||||
|
websocket-extensions (>= 0.1.0)
|
||||||
|
websocket-extensions (0.1.2)
|
||||||
xpath (2.0.0)
|
xpath (2.0.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
zipruby (0.3.6)
|
zipruby (0.3.6)
|
||||||
|
@ -238,15 +265,19 @@ DEPENDENCIES
|
||||||
capybara_minitest_spec
|
capybara_minitest_spec
|
||||||
cocaine
|
cocaine
|
||||||
dav4rack
|
dav4rack
|
||||||
|
dnsruby
|
||||||
erubis
|
erubis
|
||||||
fabrication
|
fabrication
|
||||||
faker
|
faker
|
||||||
filesize
|
filesize
|
||||||
|
geoip
|
||||||
google-api-client
|
google-api-client
|
||||||
hiredis
|
hiredis
|
||||||
|
io-extra
|
||||||
jdbc-postgres
|
jdbc-postgres
|
||||||
jruby-openssl
|
jruby-openssl
|
||||||
json
|
json
|
||||||
|
m
|
||||||
magic
|
magic
|
||||||
mail
|
mail
|
||||||
minitest
|
minitest
|
||||||
|
@ -267,6 +298,7 @@ DEPENDENCIES
|
||||||
rest-client
|
rest-client
|
||||||
rmagick
|
rmagick
|
||||||
ruby-debug
|
ruby-debug
|
||||||
|
rye
|
||||||
sass
|
sass
|
||||||
screencap
|
screencap
|
||||||
scrypt
|
scrypt
|
||||||
|
@ -282,5 +314,9 @@ DEPENDENCIES
|
||||||
stripe-ruby-mock (~> 2.0.1)
|
stripe-ruby-mock (~> 2.0.1)
|
||||||
thread
|
thread
|
||||||
tilt
|
tilt
|
||||||
|
timecop
|
||||||
webmock
|
webmock
|
||||||
zipruby
|
zipruby
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.10.2
|
||||||
|
|
41
Rakefile
41
Rakefile
|
@ -31,40 +31,11 @@ end
|
||||||
|
|
||||||
desc "parse logs"
|
desc "parse logs"
|
||||||
task :parse_logs => [:environment] do
|
task :parse_logs => [:environment] do
|
||||||
Dir[File.join($config['logs_path'], '*.log')].each do |log_path|
|
Stat.prune!
|
||||||
hits = {}
|
StatLocation.prune!
|
||||||
visits = {}
|
StatReferrer.prune!
|
||||||
visit_ips = {}
|
StatPath.prune!
|
||||||
|
Stat.parse_logfiles $config['logs_path']
|
||||||
logfile = File.open log_path, 'r'
|
|
||||||
|
|
||||||
while hit = logfile.gets
|
|
||||||
time, username, size, path, ip = hit.split ' '
|
|
||||||
|
|
||||||
hits[username] ||= 0
|
|
||||||
hits[username] += 1
|
|
||||||
|
|
||||||
visit_ips[username] = [] if !visit_ips[username]
|
|
||||||
|
|
||||||
unless visit_ips[username].include?(ip)
|
|
||||||
visits[username] ||= 0
|
|
||||||
visits[username] += 1
|
|
||||||
visit_ips[username] << ip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
logfile.close
|
|
||||||
|
|
||||||
hits.each do |username,hitcount|
|
|
||||||
DB['update sites set hits=hits+? where username=?', hitcount, username].first
|
|
||||||
end
|
|
||||||
|
|
||||||
visits.each do |username,visitcount|
|
|
||||||
DB['update sites set views=views+? where username=?', visitcount, username].first
|
|
||||||
end
|
|
||||||
|
|
||||||
FileUtils.rm log_path
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Update banned IPs list'
|
desc 'Update banned IPs list'
|
||||||
|
@ -223,7 +194,7 @@ end
|
||||||
desc 'prime_space_used'
|
desc 'prime_space_used'
|
||||||
task :prime_space_used => [:environment] do
|
task :prime_space_used => [:environment] do
|
||||||
Site.select(:id,:username,:space_used).all.each do |s|
|
Site.select(:id,:username,:space_used).all.each do |s|
|
||||||
s.space_used += s.actual_space_used
|
s.space_used = s.actual_space_used
|
||||||
s.save_changes validate: false
|
s.save_changes validate: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
3
app.rb
3
app.rb
|
@ -36,6 +36,7 @@ before do
|
||||||
end
|
end
|
||||||
|
|
||||||
not_found do
|
not_found do
|
||||||
|
@title = 'Not Found'
|
||||||
erb :'not_found'
|
erb :'not_found'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ error do
|
||||||
from: 'web@neocities.org',
|
from: 'web@neocities.org',
|
||||||
to: 'errors@neocities.org',
|
to: 'errors@neocities.org',
|
||||||
subject: "[Neocities Error] #{env['sinatra.error'].class}: #{env['sinatra.error'].message}",
|
subject: "[Neocities Error] #{env['sinatra.error'].class}: #{env['sinatra.error'].message}",
|
||||||
body: erb(:'views/templates/email/error'),
|
body: erb(:'templates/email/error', layout: false),
|
||||||
no_footer: true
|
no_footer: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
52
app/admin.rb
52
app/admin.rb
|
@ -5,6 +5,56 @@ get '/admin' do
|
||||||
erb :'admin'
|
erb :'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get '/admin/reports' do
|
||||||
|
require_admin
|
||||||
|
@reports = Report.order(:created_at.desc).all
|
||||||
|
erb :'admin/reports'
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/admin/email' do
|
||||||
|
require_admin
|
||||||
|
erb :'admin/email'
|
||||||
|
end
|
||||||
|
|
||||||
|
post '/admin/email' do
|
||||||
|
require_admin
|
||||||
|
|
||||||
|
%i{subject body}.each do |k|
|
||||||
|
if params[k].nil? || params[k].empty?
|
||||||
|
flash[:error] = "#{k.capitalize} is missing."
|
||||||
|
redirect '/admin/email'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sites = Site.newsletter_sites
|
||||||
|
|
||||||
|
day = 0
|
||||||
|
|
||||||
|
until sites.empty?
|
||||||
|
seconds = 0.0
|
||||||
|
queued_sites = []
|
||||||
|
Site::EMAIL_BLAST_MAXIMUM_PER_DAY.times {
|
||||||
|
break if sites.empty?
|
||||||
|
queued_sites << sites.pop
|
||||||
|
}
|
||||||
|
|
||||||
|
queued_sites.each do |site|
|
||||||
|
EmailWorker.perform_at((day.days.from_now + seconds), {
|
||||||
|
from: 'Kyle from Neocities <kyle@neocities.org>',
|
||||||
|
to: site.email,
|
||||||
|
subject: params[:subject],
|
||||||
|
body: params[:body]
|
||||||
|
})
|
||||||
|
seconds += 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
day += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
flash[:success] = "#{sites.length} emails have been queued, #{Site::EMAIL_BLAST_MAXIMUM_PER_DAY} per day."
|
||||||
|
redirect '/'
|
||||||
|
end
|
||||||
|
|
||||||
post '/admin/banip' do
|
post '/admin/banip' do
|
||||||
require_admin
|
require_admin
|
||||||
site = Site[username: params[:username]]
|
site = Site[username: params[:username]]
|
||||||
|
@ -18,7 +68,7 @@ post '/admin/banip' do
|
||||||
flash[:error] = 'IP is blank, cannot continue'
|
flash[:error] = 'IP is blank, cannot continue'
|
||||||
redirect '/admin'
|
redirect '/admin'
|
||||||
end
|
end
|
||||||
sites = Site.filter(ip: Site.hash_ip(site.ip), is_banned: false).all
|
sites = Site.filter(ip: site.ip, is_banned: false).all
|
||||||
sites.each {|s| s.ban!}
|
sites.each {|s| s.ban!}
|
||||||
flash[:error] = "#{sites.length} sites have been banned."
|
flash[:error] = "#{sites.length} sites have been banned."
|
||||||
redirect '/admin'
|
redirect '/admin'
|
||||||
|
|
17
app/api.rb
17
app/api.rb
|
@ -7,6 +7,7 @@ end
|
||||||
|
|
||||||
post '/api/upload' do
|
post '/api/upload' do
|
||||||
require_api_credentials
|
require_api_credentials
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
params.each do |k,v|
|
params.each do |k,v|
|
||||||
next unless v.is_a?(Hash) && v[:tempfile]
|
next unless v.is_a?(Hash) && v[:tempfile]
|
||||||
|
@ -22,6 +23,10 @@ post '/api/upload' do
|
||||||
api_error 400, 'too_large', 'files are too large to fit in your space, try uploading smaller (or less) files'
|
api_error 400, 'too_large', 'files are too large to fit in your space, try uploading smaller (or less) files'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if current_site.too_many_files?(files.length)
|
||||||
|
api_error 400, 'too_many_files', "cannot exceed the maximum site files limit (#{current_site.plan_feature(:maximum_site_files)}), #{current_site.supporter? ? 'please contact support' : 'please upgrade to a supporter account'}"
|
||||||
|
end
|
||||||
|
|
||||||
files.each do |file|
|
files.each do |file|
|
||||||
if !current_site.okay_to_upload?(file)
|
if !current_site.okay_to_upload?(file)
|
||||||
api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type (or contains not allowed content) for this site, files have not been uploaded"
|
api_error 400, 'invalid_file_type', "#{file[:filename]} is not a valid file type (or contains not allowed content) for this site, files have not been uploaded"
|
||||||
|
@ -32,13 +37,7 @@ post '/api/upload' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
results = []
|
results = current_site.store_files files
|
||||||
files.each do |file|
|
|
||||||
results << current_site.store_file(file[:filename], file[:tempfile])
|
|
||||||
end
|
|
||||||
|
|
||||||
current_site.increment_changed_count if results.include?(true)
|
|
||||||
|
|
||||||
api_success 'your file(s) have been successfully uploaded'
|
api_success 'your file(s) have been successfully uploaded'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,6 +52,10 @@ post '/api/delete' do
|
||||||
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if current_site.files_path(path) == current_site.files_path
|
||||||
|
api_error 400, 'cannot_delete_site_directory', 'cannot delete the root directory of the site'
|
||||||
|
end
|
||||||
|
|
||||||
if !current_site.file_exists?(path)
|
if !current_site.file_exists?(path)
|
||||||
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,17 +1,33 @@
|
||||||
get '/browse/?' do
|
get '/browse/?' do
|
||||||
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
|
||||||
site_dataset = browse_sites_dataset
|
|
||||||
site_dataset = site_dataset.paginate @current_page, Site::BROWSE_PAGINATION_LENGTH
|
|
||||||
@page_count = site_dataset.page_count || 1
|
|
||||||
@sites = site_dataset.all
|
|
||||||
erb :browse
|
|
||||||
end
|
|
||||||
|
|
||||||
def browse_sites_dataset
|
|
||||||
@current_page = params[:current_page]
|
@current_page = params[:current_page]
|
||||||
@current_page = @current_page.to_i
|
@current_page = @current_page.to_i
|
||||||
@current_page = 1 if @current_page == 0
|
@current_page = 1 if @current_page == 0
|
||||||
|
|
||||||
|
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
||||||
|
|
||||||
|
if is_education?
|
||||||
|
site_dataset = education_sites_dataset
|
||||||
|
else
|
||||||
|
site_dataset = browse_sites_dataset
|
||||||
|
end
|
||||||
|
|
||||||
|
site_dataset = site_dataset.paginate @current_page, Site::BROWSE_PAGINATION_LENGTH
|
||||||
|
@page_count = site_dataset.page_count || 1
|
||||||
|
@sites = site_dataset.all
|
||||||
|
if params[:tag]
|
||||||
|
@title = "Sites tagged #{params[:tag]}"
|
||||||
|
end
|
||||||
|
erb :browse
|
||||||
|
end
|
||||||
|
|
||||||
|
def education_sites_dataset
|
||||||
|
site_dataset = Site.filter is_deleted: false
|
||||||
|
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
||||||
|
params[:tag] = current_site.tags.first.name
|
||||||
|
site_dataset.where! ['tags.name = ?', params[:tag]]
|
||||||
|
end
|
||||||
|
|
||||||
|
def browse_sites_dataset
|
||||||
site_dataset = Site.filter(is_deleted: false, is_banned: false, is_crashing: false).filter(site_changed: true)
|
site_dataset = Site.filter(is_deleted: false, is_banned: false, is_crashing: false).filter(site_changed: true)
|
||||||
|
|
||||||
if current_site
|
if current_site
|
||||||
|
@ -27,6 +43,19 @@ def browse_sites_dataset
|
||||||
end
|
end
|
||||||
|
|
||||||
case params[:sort_by]
|
case params[:sort_by]
|
||||||
|
when 'followers'
|
||||||
|
site_dataset = site_dataset.association_left_join :follows
|
||||||
|
site_dataset.select_all! :sites
|
||||||
|
site_dataset.select_append! Sequel.lit("count(follows.site_id) AS follow_count")
|
||||||
|
site_dataset.group! :sites__id
|
||||||
|
site_dataset.order! :follow_count.desc, :updated_at.desc
|
||||||
|
when 'supporters'
|
||||||
|
site_dataset.exclude! plan_type: nil
|
||||||
|
site_dataset.exclude! plan_type: 'free'
|
||||||
|
site_dataset.order! :views.desc, :site_updated_at.desc
|
||||||
|
when 'featured'
|
||||||
|
site_dataset.exclude! featured_at: nil
|
||||||
|
site_dataset.order! :featured_at.desc
|
||||||
when 'hits'
|
when 'hits'
|
||||||
site_dataset.where!{views > 100}
|
site_dataset.where!{views > 100}
|
||||||
site_dataset.order!(:hits.desc, :site_updated_at.desc)
|
site_dataset.order!(:hits.desc, :site_updated_at.desc)
|
||||||
|
@ -50,16 +79,19 @@ def browse_sites_dataset
|
||||||
params[:sort_by] = 'views'
|
params[:sort_by] = 'views'
|
||||||
site_dataset.order!(:views.desc, :site_updated_at.desc)
|
site_dataset.order!(:views.desc, :site_updated_at.desc)
|
||||||
else
|
else
|
||||||
params[:sort_by] = 'last_updated'
|
site_dataset = site_dataset.association_left_join :follows
|
||||||
site_dataset.where!{views > 100}
|
site_dataset.select_all! :sites
|
||||||
site_dataset.order!(:site_updated_at.desc, :views.desc)
|
site_dataset.select_append! Sequel.lit("count(follows.site_id) AS follow_count")
|
||||||
|
site_dataset.group! :sites__id
|
||||||
|
site_dataset.order! :follow_count.desc, :updated_at.desc
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
site_dataset.where! ['sites.is_nsfw = ?', (params[:is_nsfw] == 'true' ? true : false)]
|
site_dataset.where! ['sites.is_nsfw = ?', (params[:is_nsfw] == 'true' ? true : false)]
|
||||||
|
|
||||||
if params[:tag]
|
if params[:tag]
|
||||||
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
site_dataset.inner_join! :sites_tags, :site_id => :id
|
||||||
|
site_dataset.inner_join! :tags, :id => :sites_tags__tag_id
|
||||||
site_dataset.where! ['tags.name = ?', params[:tag]]
|
site_dataset.where! ['tags.name = ?', params[:tag]]
|
||||||
site_dataset.where! ['tags.is_nsfw = ?', (params[:is_nsfw] == 'true' ? true : false)]
|
site_dataset.where! ['tags.is_nsfw = ?', (params[:is_nsfw] == 'true' ? true : false)]
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,9 +16,11 @@ def new_recaptcha_valid?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CREATE_MATCH_REGEX = /^username$|^password$|^email$|^new_tags_string$|^is_education$/
|
||||||
|
|
||||||
post '/create_validate_all' do
|
post '/create_validate_all' do
|
||||||
content_type :json
|
content_type :json
|
||||||
fields = params.select {|p| p.match /^username$|^password$|^email$|^new_tags_string$/}
|
fields = params.select {|p| p.match CREATE_MATCH_REGEX}
|
||||||
|
|
||||||
site = Site.new fields
|
site = Site.new fields
|
||||||
|
|
||||||
|
@ -33,11 +35,12 @@ end
|
||||||
post '/create_validate' do
|
post '/create_validate' do
|
||||||
content_type :json
|
content_type :json
|
||||||
|
|
||||||
if !params[:field].match /^username$|^password$|^email$|^new_tags_string$/
|
if !params[:field].match CREATE_MATCH_REGEX
|
||||||
return {error: 'not a valid field'}.to_json
|
return {error: 'not a valid field'}.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
site = Site.new(params[:field] => params[:value])
|
site = Site.new(params[:field] => params[:value])
|
||||||
|
site.is_education = params[:is_education]
|
||||||
site.valid?
|
site.valid?
|
||||||
|
|
||||||
field_sym = params[:field].to_sym
|
field_sym = params[:field].to_sym
|
||||||
|
@ -51,14 +54,23 @@ end
|
||||||
|
|
||||||
post '/create' do
|
post '/create' do
|
||||||
content_type :json
|
content_type :json
|
||||||
require_unbanned_ip
|
|
||||||
|
if banned?(true)
|
||||||
|
signout
|
||||||
|
session[:banned] = true if !session[:banned]
|
||||||
|
|
||||||
|
flash[:error] = 'There was an error, please <a href="/contact">contact support</a> to log in.'
|
||||||
|
redirect '/'
|
||||||
|
end
|
||||||
|
|
||||||
dashboard_if_signed_in
|
dashboard_if_signed_in
|
||||||
|
|
||||||
@site = Site.new(
|
@site = Site.new(
|
||||||
username: params[:username],
|
username: params[:username],
|
||||||
password: params[:password],
|
password: params[:password],
|
||||||
email: params[:email],
|
email: params[:email],
|
||||||
new_tags_string: params[:tags],
|
new_tags_string: params[:new_tags_string],
|
||||||
|
is_education: params[:is_education] == 'true' ? true : false,
|
||||||
ip: request.ip
|
ip: request.ip
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
14
app/index.rb
14
app/index.rb
|
@ -2,6 +2,8 @@ get '/?' do
|
||||||
if current_site
|
if current_site
|
||||||
require_login
|
require_login
|
||||||
|
|
||||||
|
redirect '/dashboard' if current_site.is_education
|
||||||
|
|
||||||
@suggestions = current_site.suggestions
|
@suggestions = current_site.suggestions
|
||||||
|
|
||||||
@current_page = params[:current_page].to_i
|
@current_page = params[:current_page].to_i
|
||||||
|
@ -34,7 +36,7 @@ get '/?' do
|
||||||
@sites_count = SimpleCache.get :sites_count
|
@sites_count = SimpleCache.get :sites_count
|
||||||
end
|
end
|
||||||
|
|
||||||
erb :index, layout: false
|
erb :index, layout: :index_layout
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/welcome' do
|
get '/welcome' do
|
||||||
|
@ -43,6 +45,11 @@ get '/welcome' do
|
||||||
erb :'welcome', locals: {site: current_site}
|
erb :'welcome', locals: {site: current_site}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get '/education' do
|
||||||
|
redirect '/' if signed_in?
|
||||||
|
erb :education, layout: :index_layout
|
||||||
|
end
|
||||||
|
|
||||||
get '/tutorials' do
|
get '/tutorials' do
|
||||||
erb :'tutorials'
|
erb :'tutorials'
|
||||||
end
|
end
|
||||||
|
@ -68,5 +75,10 @@ get '/press' do
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/legal/?' do
|
get '/legal/?' do
|
||||||
|
@title = 'Legal Guide to Neocities'
|
||||||
erb :'legal'
|
erb :'legal'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get '/permanent-web' do
|
||||||
|
erb :'permanent_web'
|
||||||
|
end
|
||||||
|
|
|
@ -24,4 +24,9 @@ get '/welcome_mockup' do
|
||||||
require_login
|
require_login
|
||||||
erb :'welcome_mockup', locals: {site: current_site}
|
erb :'welcome_mockup', locals: {site: current_site}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get '/stats_mockup' do
|
||||||
|
require_login
|
||||||
|
erb :'stats_mockup', locals: {site: current_site}
|
||||||
|
end
|
||||||
# :nocov:
|
# :nocov:
|
|
@ -1,5 +1,6 @@
|
||||||
get '/signin/?' do
|
get '/signin/?' do
|
||||||
dashboard_if_signed_in
|
dashboard_if_signed_in
|
||||||
|
@title = 'Sign In'
|
||||||
erb :'signin'
|
erb :'signin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
73
app/site.rb
73
app/site.rb
|
@ -9,6 +9,8 @@ get '/site/:username/?' do |username|
|
||||||
# TODO: There should probably be a "this site was deleted" page.
|
# TODO: There should probably be a "this site was deleted" page.
|
||||||
not_found if site.nil? || site.is_banned || site.is_deleted
|
not_found if site.nil? || site.is_banned || site.is_deleted
|
||||||
|
|
||||||
|
redirect '/' if site.is_education
|
||||||
|
|
||||||
@title = site.title
|
@title = site.title
|
||||||
|
|
||||||
@current_page = params[:current_page]
|
@current_page = params[:current_page]
|
||||||
|
@ -16,6 +18,7 @@ get '/site/:username/?' do |username|
|
||||||
@current_page = 1 if @current_page == 0
|
@current_page = 1 if @current_page == 0
|
||||||
|
|
||||||
if params[:event_id]
|
if params[:event_id]
|
||||||
|
not_found unless params[:event_id].is_integer?
|
||||||
event = Event.select(:id).where(id: params[:event_id]).first
|
event = Event.select(:id).where(id: params[:event_id]).first
|
||||||
not_found if event.nil?
|
not_found if event.nil?
|
||||||
events_dataset = Event.where(id: params[:event_id]).paginate(1, 1)
|
events_dataset = Event.where(id: params[:event_id]).paginate(1, 1)
|
||||||
|
@ -29,6 +32,76 @@ get '/site/:username/?' do |username|
|
||||||
erb :'site', locals: {site: site, is_current_site: site == current_site}
|
erb :'site', locals: {site: site, is_current_site: site == current_site}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get '/site/:username/archives' do
|
||||||
|
require_login
|
||||||
|
@site = Site[username: params[:username]]
|
||||||
|
not_found if @site.nil?
|
||||||
|
redirect request.referrer unless current_site.id == @site.id
|
||||||
|
|
||||||
|
@archives = @site.archives_dataset.limit(300).order(:updated_at.desc).all
|
||||||
|
|
||||||
|
erb :'site/archives'
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/site/:username/stats' do
|
||||||
|
@site = Site[username: params[:username]]
|
||||||
|
not_found if @site.nil?
|
||||||
|
|
||||||
|
@title = "Site stats for #{@site.host}"
|
||||||
|
|
||||||
|
@stats = {}
|
||||||
|
|
||||||
|
%i{referrers locations paths}.each do |stat|
|
||||||
|
@stats[stat] = @site.send("stat_#{stat}_dataset".to_sym).order(:views.desc).limit(100).all
|
||||||
|
end
|
||||||
|
|
||||||
|
@stats[:locations].collect! do |location|
|
||||||
|
location_name = ''
|
||||||
|
|
||||||
|
location_name += location.city_name if location.city_name
|
||||||
|
|
||||||
|
if location.region_name
|
||||||
|
# Some of the region names are numbers for some reason.
|
||||||
|
begin
|
||||||
|
Integer(location.region_name)
|
||||||
|
rescue
|
||||||
|
location_name += ', ' unless location_name == ''
|
||||||
|
location_name += location.region_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if location.country_code2 && !$country_codes[location.country_code2].nil?
|
||||||
|
location_name += ', ' unless location_name == ''
|
||||||
|
location_name += $country_codes[location.country_code2]
|
||||||
|
end
|
||||||
|
|
||||||
|
location_hash = {name: location_name, views: location.views}
|
||||||
|
if location.latitude && location.longitude
|
||||||
|
location_hash.merge! latitude: location.latitude, longitude: location.longitude
|
||||||
|
end
|
||||||
|
location_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
stats_dataset = @site.stats_dataset.order(:created_at.desc).exclude(created_at: Date.today)
|
||||||
|
|
||||||
|
if @site.supporter?
|
||||||
|
unless params[:days].to_s == 'sincethebigbang'
|
||||||
|
if params[:days]
|
||||||
|
stats_dataset.limit! params[:days]
|
||||||
|
else
|
||||||
|
stats_dataset.limit! 7
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stats_dataset.limit! 7
|
||||||
|
end
|
||||||
|
|
||||||
|
@stats[:stat_days] = stats_dataset.all.reverse
|
||||||
|
@multi_tooltip_template = "<%= datasetLabel %> - <%= value %>"
|
||||||
|
|
||||||
|
erb :'site/stats', locals: {site: @site}
|
||||||
|
end
|
||||||
|
|
||||||
post '/site/:username/set_editor_theme' do
|
post '/site/:username/set_editor_theme' do
|
||||||
require_login
|
require_login
|
||||||
current_site.editor_theme = params[:editor_theme]
|
current_site.editor_theme = params[:editor_theme]
|
||||||
|
|
|
@ -9,32 +9,67 @@ get '/site_files/new' do
|
||||||
redirect '/site_files/new_page'
|
redirect '/site_files/new_page'
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/site_files/create_page' do
|
post '/site_files/create' do
|
||||||
require_login
|
require_login
|
||||||
@errors = []
|
@errors = []
|
||||||
|
|
||||||
params[:pagefilename].gsub!(/[^a-zA-Z0-9_\-.]/, '')
|
filename = params[:pagefilename] || params[:filename]
|
||||||
params[:pagefilename].gsub!(/\.html$/i, '')
|
|
||||||
|
|
||||||
if params[:pagefilename].nil? || params[:pagefilename].strip.empty?
|
filename.gsub!(/[^a-zA-Z0-9_\-.]/, '')
|
||||||
@errors << 'You must provide a file name.'
|
|
||||||
halt erb(:'site_files/new_page')
|
redirect_uri = '/dashboard'
|
||||||
|
redirect_uri += "?dir=#{Rack::Utils.escape params[:dir]}" if params[:dir]
|
||||||
|
|
||||||
|
if filename.nil? || filename.strip.empty?
|
||||||
|
flash[:error] = 'You must provide a file name.'
|
||||||
|
redirect redirect_uri
|
||||||
end
|
end
|
||||||
|
|
||||||
name = "#{params[:pagefilename]}.html"
|
name = "#{filename}"
|
||||||
|
|
||||||
name = "#{params[:dir]}/#{name}" if params[:dir]
|
name = "#{params[:dir]}/#{name}" if params[:dir]
|
||||||
|
|
||||||
|
name = current_site.scrubbed_path name
|
||||||
|
|
||||||
if current_site.file_exists?(name)
|
if current_site.file_exists?(name)
|
||||||
@errors << %{Web page "#{name}" already exists! Choose another name.}
|
flash[:error] = %{Web page "#{name}" already exists! Choose another name.}
|
||||||
halt erb(:'site_files/new_page')
|
redirect redirect_uri
|
||||||
end
|
end
|
||||||
|
|
||||||
|
extname = File.extname name
|
||||||
|
|
||||||
|
unless extname.match /^\.#{Site::EDITABLE_FILE_EXT}/i
|
||||||
|
flash[:error] = "Must be an text editable file type (#{Site::VALID_EDITABLE_EXTENSIONS.join(', ')})."
|
||||||
|
redirect redirect_uri
|
||||||
|
end
|
||||||
|
|
||||||
|
site_file = current_site.site_files_dataset.where(path: name).first
|
||||||
|
|
||||||
|
if site_file
|
||||||
|
flash[:error] = 'File already exists, cannot create.'
|
||||||
|
redirect redirect_uri
|
||||||
|
end
|
||||||
|
|
||||||
|
if extname.match(/^\.html|^\.htm/i)
|
||||||
current_site.install_new_html_file name
|
current_site.install_new_html_file name
|
||||||
|
else
|
||||||
|
file_path = current_site.files_path(name)
|
||||||
|
FileUtils.touch file_path
|
||||||
|
File.chmod 0640, file_path
|
||||||
|
|
||||||
|
site_file ||= SiteFile.new site_id: current_site.id, path: name
|
||||||
|
|
||||||
|
site_file.set_all(
|
||||||
|
size: 0,
|
||||||
|
sha1_hash: Digest::SHA1.hexdigest(''),
|
||||||
|
updated_at: Time.now
|
||||||
|
)
|
||||||
|
site_file.save
|
||||||
|
end
|
||||||
|
|
||||||
flash[:success] = %{#{name} was created! <a style="color: #FFFFFF; text-decoration: underline" href="/site_files/text_editor/#{name}">Click here to edit it</a>.}
|
flash[:success] = %{#{name} was created! <a style="color: #FFFFFF; text-decoration: underline" href="/site_files/text_editor/#{name}">Click here to edit it</a>.}
|
||||||
|
|
||||||
redirect params[:dir] ? "/dashboard?dir=#{Rack::Utils.escape params[:dir]}" : '/dashboard'
|
redirect redirect_uri
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_upload_response(error=nil)
|
def file_upload_response(error=nil)
|
||||||
|
@ -59,8 +94,22 @@ post '/site_files/upload' do
|
||||||
file_upload_response "Uploaded files were not seen by the server, cancelled. We don't know what's causing this yet. Please contact us so we can help fix it. Thanks!"
|
file_upload_response "Uploaded files were not seen by the server, cancelled. We don't know what's causing this yet. Please contact us so we can help fix it. Thanks!"
|
||||||
end
|
end
|
||||||
|
|
||||||
params[:files].each do |file|
|
params[:files].each_with_index do |file,i|
|
||||||
file[:filename] = "#{params[:dir]}/#{file[:filename]}" if params[:dir]
|
dir_name = ''
|
||||||
|
dir_name = params[:dir] if params[:dir]
|
||||||
|
|
||||||
|
unless params[:file_paths].nil? || params[:file_paths].empty? || params[:file_paths].length == 0
|
||||||
|
|
||||||
|
file_path = params[:file_paths].select {|file_path|
|
||||||
|
file[:filename] == Pathname(file_path).basename.to_s
|
||||||
|
}.first
|
||||||
|
|
||||||
|
unless file_path.nil?
|
||||||
|
dir_name += '/' + Pathname(file_path).dirname.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
file[:filename] = "#{dir_name}/#{file[:filename]}"
|
||||||
if current_site.file_size_too_large? file[:tempfile].size
|
if current_site.file_size_too_large? file[:tempfile].size
|
||||||
file_upload_response "#{file[:filename]} is too large, upload cancelled."
|
file_upload_response "#{file[:filename]} is too large, upload cancelled."
|
||||||
end
|
end
|
||||||
|
@ -75,21 +124,23 @@ post '/site_files/upload' do
|
||||||
file_upload_response "File(s) do not fit in your available space, upload cancelled."
|
file_upload_response "File(s) do not fit in your available space, upload cancelled."
|
||||||
end
|
end
|
||||||
|
|
||||||
results = []
|
if current_site.too_many_files? params[:files].length
|
||||||
params[:files].each do |file|
|
file_upload_response "Too many files, cannot upload"
|
||||||
results << current_site.store_file(file[:filename], file[:tempfile])
|
|
||||||
end
|
end
|
||||||
current_site.increment_changed_count if results.include?(true)
|
|
||||||
|
|
||||||
|
results = current_site.store_files params[:files]
|
||||||
file_upload_response
|
file_upload_response
|
||||||
end
|
end
|
||||||
|
|
||||||
post '/site_files/delete' do
|
post '/site_files/delete' do
|
||||||
require_login
|
require_login
|
||||||
current_site.delete_file params[:filename]
|
current_site.delete_file params[:filename]
|
||||||
|
|
||||||
flash[:success] = "Deleted #{params[:filename]}."
|
flash[:success] = "Deleted #{params[:filename]}."
|
||||||
redirect '/dashboard'
|
|
||||||
|
dirname = Pathname(params[:filename]).dirname
|
||||||
|
dir_query = dirname.nil? || dirname.to_s == '.' ? '' : "?dir=#{Rack::Utils.escape dirname}"
|
||||||
|
|
||||||
|
redirect "/dashboard#{dir_query}"
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/site_files/:username.zip' do |username|
|
get '/site_files/:username.zip' do |username|
|
||||||
|
@ -147,7 +198,7 @@ post %r{\/site_files\/save\/(.+)} do
|
||||||
halt 'File is too large to fit in your space, it has NOT been saved. You will need to reduce the size or upgrade to a new plan.'
|
halt 'File is too large to fit in your space, it has NOT been saved. You will need to reduce the size or upgrade to a new plan.'
|
||||||
end
|
end
|
||||||
|
|
||||||
current_site.store_file filename, tempfile
|
current_site.store_files [{filename: filename, tempfile: tempfile}]
|
||||||
|
|
||||||
'ok'
|
'ok'
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,8 @@ get '/stats/?' do
|
||||||
# expires 14400, :public, :must_revalidate if self.class.production? # 4 hours
|
# expires 14400, :public, :must_revalidate if self.class.production? # 4 hours
|
||||||
|
|
||||||
@stats = {
|
@stats = {
|
||||||
|
total_hosted_site_hits: DB['SELECT SUM(hits) FROM sites'].first[:sum],
|
||||||
|
total_hosted_site_views: DB['SELECT SUM(views) FROM sites'].first[:sum],
|
||||||
total_sites: Site.count,
|
total_sites: Site.count,
|
||||||
total_unbanned_sites: Site.where(is_banned: false).count,
|
total_unbanned_sites: Site.where(is_banned: false).count,
|
||||||
total_banned_sites: Site.where(is_banned: true).count,
|
total_banned_sites: Site.where(is_banned: true).count,
|
||||||
|
|
|
@ -1,3 +1,23 @@
|
||||||
|
def stripe_get_site_from_event(event)
|
||||||
|
customer_id = event['data']['object']['customer']
|
||||||
|
customer = Stripe::Customer.retrieve customer_id
|
||||||
|
|
||||||
|
# Some old accounts only have a username for the desc
|
||||||
|
desc_split = customer.description.split(' - ')
|
||||||
|
|
||||||
|
if desc_split.length == 1
|
||||||
|
site_where = {username: desc_split.first}
|
||||||
|
end
|
||||||
|
|
||||||
|
if desc_split.last.to_i == 0
|
||||||
|
site_where = {username: desc_split.first}
|
||||||
|
else
|
||||||
|
site_where = {id: desc_split.last}
|
||||||
|
end
|
||||||
|
|
||||||
|
Site.where(site_where).first
|
||||||
|
end
|
||||||
|
|
||||||
post '/stripe_webhook' do
|
post '/stripe_webhook' do
|
||||||
event = JSON.parse request.body.read
|
event = JSON.parse request.body.read
|
||||||
if event['type'] == 'customer.created'
|
if event['type'] == 'customer.created'
|
||||||
|
@ -14,8 +34,7 @@ post '/stripe_webhook' do
|
||||||
end
|
end
|
||||||
|
|
||||||
if event['type'] == 'charge.failed'
|
if event['type'] == 'charge.failed'
|
||||||
site_id = event['data']['object']['description'].split(' - ').last
|
site = stripe_get_site_from_event event
|
||||||
site = Site[site_id]
|
|
||||||
|
|
||||||
EmailWorker.perform_async({
|
EmailWorker.perform_async({
|
||||||
from: 'web@neocities.org',
|
from: 'web@neocities.org',
|
||||||
|
@ -26,8 +45,7 @@ post '/stripe_webhook' do
|
||||||
end
|
end
|
||||||
|
|
||||||
if event['type'] == 'customer.subscription.deleted'
|
if event['type'] == 'customer.subscription.deleted'
|
||||||
site_id = event['data']['object']['description'].split(' - ').last
|
site = stripe_get_site_from_event event
|
||||||
site = Site[site_id]
|
|
||||||
site.stripe_subscription_id = nil
|
site.stripe_subscription_id = nil
|
||||||
site.plan_type = nil
|
site.plan_type = nil
|
||||||
site.save_changes validate: false
|
site.save_changes validate: false
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
get '/surf/?' do
|
get '/surf/?' do
|
||||||
|
@current_page = params[:current_page].to_i || 1
|
||||||
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
||||||
site_dataset = browse_sites_dataset
|
site_dataset = browse_sites_dataset
|
||||||
site_dataset = site_dataset.paginate @current_page, 1
|
site_dataset = site_dataset.paginate @current_page, 1
|
||||||
@page_count = site_dataset.page_count || 1
|
@page_count = site_dataset.page_count || 1
|
||||||
@site = site_dataset.first
|
@site = site_dataset.first
|
||||||
redirect "/browse?#{Rack::Utils.build_query params}" if @site.nil?
|
redirect "/browse?#{Rack::Utils.build_query params}" if @site.nil?
|
||||||
|
@title = "Surf Mode - #{@site.title}"
|
||||||
erb :'surf', layout: false
|
erb :'surf', layout: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
|
def kickstarter_days_remaining
|
||||||
|
ending = Time.parse('Sat, Jul 25 2015 3:05 PM PDT')
|
||||||
|
today = Time.now
|
||||||
|
|
||||||
|
remaining = ending - today
|
||||||
|
return 0 if remaining < 0
|
||||||
|
|
||||||
|
((ending - today) / 86400).to_i
|
||||||
|
end
|
||||||
|
|
||||||
def dashboard_if_signed_in
|
def dashboard_if_signed_in
|
||||||
redirect '/dashboard' if signed_in?
|
redirect '/dashboard' if signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_login_ajax
|
def require_login_ajax
|
||||||
halt 'You are not logged in!' unless signed_in?
|
halt 'You are not logged in!' unless signed_in?
|
||||||
halt 'You are banned.' if current_site.is_banned? || parent_site.is_banned?
|
halt 'Please contact support.' if banned?
|
||||||
end
|
end
|
||||||
|
|
||||||
def csrf_safe?
|
def csrf_safe?
|
||||||
|
@ -15,13 +25,13 @@ def csrf_token
|
||||||
session[:_csrf_token] ||= SecureRandom.base64(32)
|
session[:_csrf_token] ||= SecureRandom.base64(32)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_education?
|
||||||
|
current_site && current_site.is_education
|
||||||
|
end
|
||||||
|
|
||||||
def require_login
|
def require_login
|
||||||
redirect '/' unless signed_in?
|
redirect '/' unless signed_in?
|
||||||
if session[:banned] || current_site.is_banned || parent_site.is_banned
|
enforce_ban if banned?
|
||||||
signout
|
|
||||||
session[:banned] = true
|
|
||||||
redirect '/'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def signed_in?
|
def signed_in?
|
||||||
|
@ -38,14 +48,18 @@ def parent_site
|
||||||
current_site.parent? ? current_site : current_site.parent
|
current_site.parent? ? current_site : current_site.parent
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_unbanned_ip
|
def banned?(ip_check=false)
|
||||||
if session[:banned] || Site.banned_ip?(request.ip)
|
return true if session[:banned]
|
||||||
|
return true if current_site && (current_site.is_banned || parent_site.is_banned)
|
||||||
|
|
||||||
|
return true if ip_check && Site.banned_ip?(request.ip)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def enforce_ban
|
||||||
signout
|
signout
|
||||||
session[:banned] = true
|
session[:banned] = true
|
||||||
flash[:error] = 'Site creation has been banned due to ToS violation/spam. '+
|
redirect '/'
|
||||||
'If you believe this to be in error, <a href="/contact">contact the site admin</a>.'
|
|
||||||
return {result: 'error'}.to_json
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def title
|
def title
|
||||||
|
@ -76,3 +90,31 @@ def send_confirmation_email(site=current_site)
|
||||||
body: Tilt.new('./views/templates/email_confirm.erb', pretty: true).render(self, site: site)
|
body: Tilt.new('./views/templates/email_confirm.erb', pretty: true).render(self, site: site)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def plan_pricing_button(plan_type)
|
||||||
|
plan_type = plan_type.to_s
|
||||||
|
|
||||||
|
if !parent_site
|
||||||
|
%{<a href="/#new" class="btn-Action">Sign Up</a>}
|
||||||
|
elsif parent_site && parent_site.plan_type == plan_type
|
||||||
|
if request.path.match /\/welcome/
|
||||||
|
%{<a href="/" class="btn-Action">Get Started</a>}
|
||||||
|
else
|
||||||
|
%{<div class="current-plan">Current Plan</div>}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
#if plan_type == 'supporter'
|
||||||
|
# plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]*12}, once per year"
|
||||||
|
#else
|
||||||
|
plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]}, monthly"
|
||||||
|
#end
|
||||||
|
|
||||||
|
if request.path.match /\/welcome/
|
||||||
|
button_title = 'Get Started'
|
||||||
|
else
|
||||||
|
button_title = parent_site.plan_type == 'free' ? 'Upgrade' : 'Change'
|
||||||
|
end
|
||||||
|
|
||||||
|
%{<a data-plan_name="#{Site::PLAN_FEATURES[plan_type.to_sym][:name]}" data-plan_type="#{plan_type}" data-plan_price="#{plan_price}" onclick="card = new Skeuocard($('#skeuocard')); return false" class="btn-Action planPricingButton">#{button_title}</a>}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -7,3 +7,4 @@ phantomjs_url:
|
||||||
- http://localhost:8910
|
- http://localhost:8910
|
||||||
ip_hash_salt: "400$8$1$fc21863da5d531c1"
|
ip_hash_salt: "400$8$1$fc21863da5d531c1"
|
||||||
email_unsubscribe_token: "somethingrandomderrrrp"
|
email_unsubscribe_token: "somethingrandomderrrrp"
|
||||||
|
logs_path: "/tmp/neocitiestestlogs"
|
||||||
|
|
|
@ -61,6 +61,7 @@ end
|
||||||
# :nocov:
|
# :nocov:
|
||||||
if ENV['RACK_ENV'] == 'development'
|
if ENV['RACK_ENV'] == 'development'
|
||||||
# Run async jobs immediately in development.
|
# Run async jobs immediately in development.
|
||||||
|
=begin
|
||||||
module Sidekiq
|
module Sidekiq
|
||||||
module Worker
|
module Worker
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
@ -72,18 +73,7 @@ if ENV['RACK_ENV'] == 'development'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
=end
|
||||||
# :nocov:
|
|
||||||
|
|
||||||
# :nocov:
|
|
||||||
if $config['pubsub_url']
|
|
||||||
$pubsub_pool = ConnectionPool.new(size: 10, timeout: 5) {
|
|
||||||
Redis.new url: $config['pubsub_url']
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if $config['pubsub_url'].nil? && ENV['RACK_ENV'] == 'production'
|
|
||||||
raise 'pubsub_url is missing from config'
|
|
||||||
end
|
end
|
||||||
# :nocov:
|
# :nocov:
|
||||||
|
|
||||||
|
@ -131,3 +121,11 @@ if ENV['RACK_ENV'] != 'development'
|
||||||
# Sass::Plugin.options[:never_update] = true
|
# Sass::Plugin.options[:never_update] = true
|
||||||
Sass::Plugin.options[:full_exception] = false
|
Sass::Plugin.options[:full_exception] = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'csv'
|
||||||
|
|
||||||
|
$country_codes = {}
|
||||||
|
|
||||||
|
CSV.foreach("./files/country_codes.csv") do |row|
|
||||||
|
$country_codes[row.last] = row.first
|
||||||
|
end
|
||||||
|
|
|
@ -10,15 +10,28 @@ class Numeric
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_bytes_pretty
|
def to_bytes_pretty
|
||||||
space = (self.to_f / ONE_MEGABYTE).round(2)
|
computed = nil
|
||||||
space = space.to_i if space.denominator == 1
|
unit = nil
|
||||||
# if space >= 1000000
|
{
|
||||||
# "#{space/1000000} TB"
|
'B' => 1000,
|
||||||
if space >= 1000
|
'KB' => 1000 * 1000,
|
||||||
"#{space/1000} GB"
|
'MB' => 1000 * 1000 * 1000,
|
||||||
else
|
'GB' => 1000 * 1000 * 1000 * 1000,
|
||||||
"#{space} MB"
|
'TB' => 1000 * 1000 * 1000 * 1000 * 1000
|
||||||
|
}.each_pair { |e, s|
|
||||||
|
if self < s
|
||||||
|
computed = (self.to_f / (s / 1000)).round(2)
|
||||||
|
unit = e
|
||||||
|
break
|
||||||
end
|
end
|
||||||
|
}
|
||||||
|
computed = computed.to_i if computed.modulo(1) == 0.0
|
||||||
|
|
||||||
|
"#{computed} #{unit}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_comma_separated
|
||||||
|
self.to_s.chars.to_a.reverse.each_slice(3).map(&:join).join(",").reverse
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_large_number
|
def format_large_number
|
||||||
|
|
|
@ -11,4 +11,8 @@ class String
|
||||||
def unindent
|
def unindent
|
||||||
gsub /^#{scan(/^\s*/).min_by{|l|l.length}}/, ""
|
gsub /^#{scan(/^\s*/).min_by{|l|l.length}}/, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_integer?
|
||||||
|
true if Integer(self) rescue false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
BIN
files/GeoLiteCity.dat
Normal file
BIN
files/GeoLiteCity.dat
Normal file
Binary file not shown.
250
files/country_codes.csv
Normal file
250
files/country_codes.csv
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
Afghanistan,AF
|
||||||
|
Åland Islands,AX
|
||||||
|
Albania,AL
|
||||||
|
Algeria,DZ
|
||||||
|
American Samoa,AS
|
||||||
|
Andorra,AD
|
||||||
|
Angola,AO
|
||||||
|
Anguilla,AI
|
||||||
|
Antarctica,AQ
|
||||||
|
Antigua and Barbuda,AG
|
||||||
|
Argentina,AR
|
||||||
|
Armenia,AM
|
||||||
|
Aruba,AW
|
||||||
|
Australia,AU
|
||||||
|
Austria,AT
|
||||||
|
Azerbaijan,AZ
|
||||||
|
Bahamas,BS
|
||||||
|
Bahrain,BH
|
||||||
|
Bangladesh,BD
|
||||||
|
Barbados,BB
|
||||||
|
Belarus,BY
|
||||||
|
Belgium,BE
|
||||||
|
Belize,BZ
|
||||||
|
Benin,BJ
|
||||||
|
Bermuda,BM
|
||||||
|
Bhutan,BT
|
||||||
|
"Bolivia, Plurinational State of",BO
|
||||||
|
"Bonaire, Sint Eustatius and Saba",BQ
|
||||||
|
Bosnia and Herzegovina,BA
|
||||||
|
Botswana,BW
|
||||||
|
Bouvet Island,BV
|
||||||
|
Brazil,BR
|
||||||
|
British Indian Ocean Territory,IO
|
||||||
|
Brunei Darussalam,BN
|
||||||
|
Bulgaria,BG
|
||||||
|
Burkina Faso,BF
|
||||||
|
Burundi,BI
|
||||||
|
Cambodia,KH
|
||||||
|
Cameroon,CM
|
||||||
|
Canada,CA
|
||||||
|
Cape Verde,CV
|
||||||
|
Cayman Islands,KY
|
||||||
|
Central African Republic,CF
|
||||||
|
Chad,TD
|
||||||
|
Chile,CL
|
||||||
|
China,CN
|
||||||
|
Christmas Island,CX
|
||||||
|
Cocos (Keeling) Islands,CC
|
||||||
|
Colombia,CO
|
||||||
|
Comoros,KM
|
||||||
|
Congo,CG
|
||||||
|
"Congo, the Democratic Republic of the",CD
|
||||||
|
Cook Islands,CK
|
||||||
|
Costa Rica,CR
|
||||||
|
Côte d'Ivoire,CI
|
||||||
|
Croatia,HR
|
||||||
|
Cuba,CU
|
||||||
|
Curaçao,CW
|
||||||
|
Cyprus,CY
|
||||||
|
Czech Republic,CZ
|
||||||
|
Denmark,DK
|
||||||
|
Djibouti,DJ
|
||||||
|
Dominica,DM
|
||||||
|
Dominican Republic,DO
|
||||||
|
Ecuador,EC
|
||||||
|
Egypt,EG
|
||||||
|
El Salvador,SV
|
||||||
|
Equatorial Guinea,GQ
|
||||||
|
Eritrea,ER
|
||||||
|
Estonia,EE
|
||||||
|
Ethiopia,ET
|
||||||
|
Falkland Islands (Malvinas),FK
|
||||||
|
Faroe Islands,FO
|
||||||
|
Fiji,FJ
|
||||||
|
Finland,FI
|
||||||
|
France,FR
|
||||||
|
French Guiana,GF
|
||||||
|
French Polynesia,PF
|
||||||
|
French Southern Territories,TF
|
||||||
|
Gabon,GA
|
||||||
|
Gambia,GM
|
||||||
|
Georgia,GE
|
||||||
|
Germany,DE
|
||||||
|
Ghana,GH
|
||||||
|
Gibraltar,GI
|
||||||
|
Greece,GR
|
||||||
|
Greenland,GL
|
||||||
|
Grenada,GD
|
||||||
|
Guadeloupe,GP
|
||||||
|
Guam,GU
|
||||||
|
Guatemala,GT
|
||||||
|
Guernsey,GG
|
||||||
|
Guinea,GN
|
||||||
|
Guinea-Bissau,GW
|
||||||
|
Guyana,GY
|
||||||
|
Haiti,HT
|
||||||
|
Heard Island and McDonald Mcdonald Islands,HM
|
||||||
|
Holy See (Vatican City State),VA
|
||||||
|
Honduras,HN
|
||||||
|
Hong Kong,HK
|
||||||
|
Hungary,HU
|
||||||
|
Iceland,IS
|
||||||
|
India,IN
|
||||||
|
Indonesia,ID
|
||||||
|
"Iran, Islamic Republic of",IR
|
||||||
|
Iraq,IQ
|
||||||
|
Ireland,IE
|
||||||
|
Isle of Man,IM
|
||||||
|
Israel,IL
|
||||||
|
Italy,IT
|
||||||
|
Jamaica,JM
|
||||||
|
Japan,JP
|
||||||
|
Jersey,JE
|
||||||
|
Jordan,JO
|
||||||
|
Kazakhstan,KZ
|
||||||
|
Kenya,KE
|
||||||
|
Kiribati,KI
|
||||||
|
"Korea, Democratic People's Republic of",KP
|
||||||
|
"Korea, Republic of",KR
|
||||||
|
Kuwait,KW
|
||||||
|
Kyrgyzstan,KG
|
||||||
|
Lao People's Democratic Republic,LA
|
||||||
|
Latvia,LV
|
||||||
|
Lebanon,LB
|
||||||
|
Lesotho,LS
|
||||||
|
Liberia,LR
|
||||||
|
Libya,LY
|
||||||
|
Liechtenstein,LI
|
||||||
|
Lithuania,LT
|
||||||
|
Luxembourg,LU
|
||||||
|
Macao,MO
|
||||||
|
"Macedonia, the Former Yugoslav Republic of",MK
|
||||||
|
Madagascar,MG
|
||||||
|
Malawi,MW
|
||||||
|
Malaysia,MY
|
||||||
|
Maldives,MV
|
||||||
|
Mali,ML
|
||||||
|
Malta,MT
|
||||||
|
Marshall Islands,MH
|
||||||
|
Martinique,MQ
|
||||||
|
Mauritania,MR
|
||||||
|
Mauritius,MU
|
||||||
|
Mayotte,YT
|
||||||
|
Mexico,MX
|
||||||
|
"Micronesia, Federated States of",FM
|
||||||
|
"Moldova, Republic of",MD
|
||||||
|
Monaco,MC
|
||||||
|
Mongolia,MN
|
||||||
|
Montenegro,ME
|
||||||
|
Montserrat,MS
|
||||||
|
Morocco,MA
|
||||||
|
Mozambique,MZ
|
||||||
|
Myanmar,MM
|
||||||
|
Namibia,NA
|
||||||
|
Nauru,NR
|
||||||
|
Nepal,NP
|
||||||
|
Netherlands,NL
|
||||||
|
New Caledonia,NC
|
||||||
|
New Zealand,NZ
|
||||||
|
Nicaragua,NI
|
||||||
|
Niger,NE
|
||||||
|
Nigeria,NG
|
||||||
|
Niue,NU
|
||||||
|
Norfolk Island,NF
|
||||||
|
Northern Mariana Islands,MP
|
||||||
|
Norway,NO
|
||||||
|
Oman,OM
|
||||||
|
Pakistan,PK
|
||||||
|
Palau,PW
|
||||||
|
"Palestine, State of",PS
|
||||||
|
Panama,PA
|
||||||
|
Papua New Guinea,PG
|
||||||
|
Paraguay,PY
|
||||||
|
Peru,PE
|
||||||
|
Philippines,PH
|
||||||
|
Pitcairn,PN
|
||||||
|
Poland,PL
|
||||||
|
Portugal,PT
|
||||||
|
Puerto Rico,PR
|
||||||
|
Qatar,QA
|
||||||
|
Réunion,RE
|
||||||
|
Romania,RO
|
||||||
|
Russian Federation,RU
|
||||||
|
Rwanda,RW
|
||||||
|
Saint Barthélemy,BL
|
||||||
|
"Saint Helena, Ascension and Tristan da Cunha",SH
|
||||||
|
Saint Kitts and Nevis,KN
|
||||||
|
Saint Lucia,LC
|
||||||
|
Saint Martin (French part),MF
|
||||||
|
Saint Pierre and Miquelon,PM
|
||||||
|
Saint Vincent and the Grenadines,VC
|
||||||
|
Samoa,WS
|
||||||
|
San Marino,SM
|
||||||
|
Sao Tome and Principe,ST
|
||||||
|
Saudi Arabia,SA
|
||||||
|
Senegal,SN
|
||||||
|
Serbia,RS
|
||||||
|
Seychelles,SC
|
||||||
|
Sierra Leone,SL
|
||||||
|
Singapore,SG
|
||||||
|
Sint Maarten (Dutch part),SX
|
||||||
|
Slovakia,SK
|
||||||
|
Slovenia,SI
|
||||||
|
Solomon Islands,SB
|
||||||
|
Somalia,SO
|
||||||
|
South Africa,ZA
|
||||||
|
South Georgia and the South Sandwich Islands,GS
|
||||||
|
South Sudan,SS
|
||||||
|
Spain,ES
|
||||||
|
Sri Lanka,LK
|
||||||
|
Sudan,SD
|
||||||
|
Suriname,SR
|
||||||
|
Svalbard and Jan Mayen,SJ
|
||||||
|
Swaziland,SZ
|
||||||
|
Sweden,SE
|
||||||
|
Switzerland,CH
|
||||||
|
Syrian Arab Republic,SY
|
||||||
|
"Taiwan, Province of China",TW
|
||||||
|
Tajikistan,TJ
|
||||||
|
"Tanzania, United Republic of",TZ
|
||||||
|
Thailand,TH
|
||||||
|
Timor-Leste,TL
|
||||||
|
Togo,TG
|
||||||
|
Tokelau,TK
|
||||||
|
Tonga,TO
|
||||||
|
Trinidad and Tobago,TT
|
||||||
|
Tunisia,TN
|
||||||
|
Turkey,TR
|
||||||
|
Turkmenistan,TM
|
||||||
|
Turks and Caicos Islands,TC
|
||||||
|
Tuvalu,TV
|
||||||
|
Uganda,UG
|
||||||
|
Ukraine,UA
|
||||||
|
United Arab Emirates,AE
|
||||||
|
United Kingdom,GB
|
||||||
|
United States,US
|
||||||
|
United States Minor Outlying Islands,UM
|
||||||
|
Uruguay,UY
|
||||||
|
Uzbekistan,UZ
|
||||||
|
Vanuatu,VU
|
||||||
|
"Venezuela, Bolivarian Republic of",VE
|
||||||
|
Viet Nam,VN
|
||||||
|
"Virgin Islands, British",VG
|
||||||
|
"Virgin Islands, U.S.",VI
|
||||||
|
Wallis and Futuna,WF
|
||||||
|
Western Sahara,EH
|
||||||
|
Yemen,YE
|
||||||
|
Zambia,ZM
|
||||||
|
Zimbabwe,ZW
|
||||||
|
European Union, EU
|
|
85
files/fullhitsmigration.rb
Normal file
85
files/fullhitsmigration.rb
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
raise 'nope'
|
||||||
|
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
raise 'derp'
|
||||||
|
DB.drop_table :stats
|
||||||
|
DB.drop_table :stat_referrers
|
||||||
|
DB.drop_table :stat_paths
|
||||||
|
DB.drop_table :stat_locations
|
||||||
|
|
||||||
|
DB.create_table! :hits do
|
||||||
|
primary_key :id
|
||||||
|
Integer :site_id, index: true
|
||||||
|
Integer :hit_referrer_id
|
||||||
|
Integer :hit_path_id
|
||||||
|
Integer :hit_location_id
|
||||||
|
Bignum :bandwidth
|
||||||
|
Time :accessed_at, index: true
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :hit_referrers do
|
||||||
|
primary_key :id
|
||||||
|
String :uri, index: {unique: true}
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :hit_locations do
|
||||||
|
primary_key :id
|
||||||
|
String :country_code2
|
||||||
|
String :region_name
|
||||||
|
String :city_name
|
||||||
|
Float :latitude
|
||||||
|
Float :longitude
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :hit_paths do
|
||||||
|
primary_key :id
|
||||||
|
String :path, index: {unique: true}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
raise 'No.' if ENV['RACK_ENV'] == 'production'
|
||||||
|
|
||||||
|
%i{hits hit_referrers hit_locations hit_paths}.each do |t|
|
||||||
|
DB.drop_table t
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :stats do
|
||||||
|
primary_key :id
|
||||||
|
Integer :site_id
|
||||||
|
Date :created_at
|
||||||
|
Integer :hits
|
||||||
|
Integer :views
|
||||||
|
Integer :comments
|
||||||
|
Integer :follows
|
||||||
|
Integer :site_updates
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :stat_referrers do
|
||||||
|
primary_key :id
|
||||||
|
Integer :stat_id
|
||||||
|
String :url
|
||||||
|
String :views
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :stat_locations do
|
||||||
|
primary_key :id
|
||||||
|
Integer :stat_id
|
||||||
|
String :country_code2
|
||||||
|
String :region_name
|
||||||
|
String :city_name
|
||||||
|
Decimal :latitude
|
||||||
|
Decimal :longitude
|
||||||
|
Integer :views
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table :stat_paths do
|
||||||
|
primary_key :id
|
||||||
|
Integer :stat_id
|
||||||
|
String :name
|
||||||
|
Integer :views
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
11
migrations/057_add_paypal_data.rb
Normal file
11
migrations/057_add_paypal_data.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB.add_column :sites, :paypal_profile_id, String
|
||||||
|
DB.add_column :sites, :paypal_token, String
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB.drop_column :sites, :paypal_profile_id
|
||||||
|
DB.drop_column :sites, :paypal_token
|
||||||
|
}
|
||||||
|
end
|
9
migrations/058_add_paypal_status.rb
Normal file
9
migrations/058_add_paypal_status.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB.add_column :sites, :paypal_active, :boolean, default: false
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB.drop_column :sites, :paypal_active
|
||||||
|
}
|
||||||
|
end
|
55
migrations/059_refactor_stats.rb
Normal file
55
migrations/059_refactor_stats.rb
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB.drop_table :stats
|
||||||
|
DB.create_table! :stats do
|
||||||
|
primary_key :id
|
||||||
|
Integer :site_id, index: true
|
||||||
|
Date :created_at, index: true
|
||||||
|
Integer :hits, default: 0
|
||||||
|
Integer :views, default: 0
|
||||||
|
Integer :comments, default: 0
|
||||||
|
Integer :follows, default: 0
|
||||||
|
Integer :site_updates, default: 0
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :stat_referrers do
|
||||||
|
primary_key :id
|
||||||
|
Integer :stat_id, index: true
|
||||||
|
String :url
|
||||||
|
Integer :views, default: 0
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :stat_locations do
|
||||||
|
primary_key :id
|
||||||
|
Integer :stat_id, index: true
|
||||||
|
String :country_code2
|
||||||
|
String :region_name
|
||||||
|
String :city_name
|
||||||
|
Decimal :latitude
|
||||||
|
Decimal :longitude
|
||||||
|
Integer :views, default: 0
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.create_table! :stat_paths do
|
||||||
|
primary_key :id
|
||||||
|
Integer :stat_id, index: true
|
||||||
|
String :name
|
||||||
|
Integer :views, default: 0
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB.drop_table :stats
|
||||||
|
DB.create_table! :stats do
|
||||||
|
primary_key :id
|
||||||
|
Integer :site_id, index: true
|
||||||
|
Integer :hits, default: 0
|
||||||
|
Integer :views, default: 0
|
||||||
|
DateTime :created_at, index: true
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.drop_table :stat_referrers
|
||||||
|
DB.drop_table :stat_locations
|
||||||
|
DB.drop_table :stat_paths
|
||||||
|
}
|
||||||
|
end
|
18
migrations/060_separate_stat_timestamps.rb
Normal file
18
migrations/060_separate_stat_timestamps.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# This migration detaches stat_referrers, stat_locations and stat_paths
|
||||||
|
# from stats. Instead of stat_id, we'll add a created_at timestamp and remove
|
||||||
|
# after 7 days for both free and supporter plans (for now).
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||||
|
drop_column stat_table, :stat_id
|
||||||
|
add_column stat_table, :created_at, :date, index: true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||||
|
drop_column stat_table, :created_at
|
||||||
|
add_column stat_table, :stat_id, :integer, index: true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
16
migrations/061_add_site_ids.rb
Normal file
16
migrations/061_add_site_ids.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# This migration detaches stat_referrers, stat_locations and stat_paths
|
||||||
|
# from stats. Instead of stat_id, we'll add a created_at timestamp and remove
|
||||||
|
# after 7 days for both free and supporter plans (for now).
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||||
|
add_column stat_table, :site_id, :integer, index: true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||||
|
drop_column stat_table, :site_id
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
12
migrations/062_fix_latlng.rb
Normal file
12
migrations/062_fix_latlng.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
drop_column :stat_locations, :latitude
|
||||||
|
drop_column :stat_locations, :longitude
|
||||||
|
add_column :stat_locations, :latitude, :float
|
||||||
|
add_column :stat_locations, :longitude, :float
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
# meh.
|
||||||
|
}
|
||||||
|
end
|
9
migrations/063_add_bandwidth_to_stats.rb
Normal file
9
migrations/063_add_bandwidth_to_stats.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
add_column :stats, :bandwidth, :bigint, default: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
drop_column :stats, :bandwidth
|
||||||
|
}
|
||||||
|
end
|
9
migrations/064_add_education_to_sites.rb
Normal file
9
migrations/064_add_education_to_sites.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
add_column :sites, :is_education, :boolean, default: false
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
drop_column :sites, :is_education
|
||||||
|
}
|
||||||
|
end
|
14
migrations/065_add_ipfs.rb
Normal file
14
migrations/065_add_ipfs.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB.create_table! :archives do
|
||||||
|
Integer :site_id, index: true
|
||||||
|
String :ipfs_hash
|
||||||
|
DateTime :updated_at, index: true
|
||||||
|
unique [:site_id, :ipfs_hash]
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB.drop_table :archives
|
||||||
|
}
|
||||||
|
end
|
9
migrations/066_add_username_index_to_sites.rb
Normal file
9
migrations/066_add_username_index_to_sites.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB.add_index :sites, :username
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB.drop_index :sites, :username
|
||||||
|
}
|
||||||
|
end
|
13
migrations/067_add_missing_stat_indexes.rb
Normal file
13
migrations/067_add_missing_stat_indexes.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB['create index stat_referrers_hash_multi on stat_referrers (site_id, md5(url))'].first
|
||||||
|
DB.add_index :stat_locations, :site_id
|
||||||
|
DB.add_index :stat_paths, :site_id
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB['drop index stat_referrers_hash_multi'].first
|
||||||
|
DB.drop_index :stat_locations, :site_id
|
||||||
|
DB.drop_index :stat_paths, :site_id
|
||||||
|
}
|
||||||
|
end
|
9
migrations/068_add_stat_referrer_site_id_index.rb
Normal file
9
migrations/068_add_stat_referrer_site_id_index.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
DB.add_index :stat_referrers, :site_id
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
DB.drop_index :stat_referrers, :site_id
|
||||||
|
}
|
||||||
|
end
|
13
migrations/069_add_stat_created_indexes.rb
Normal file
13
migrations/069_add_stat_created_indexes.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Sequel.migration do
|
||||||
|
up {
|
||||||
|
%i{stat_referrers stat_locations stat_paths}.each do |t|
|
||||||
|
DB.add_index t, :created_at
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
down {
|
||||||
|
%i{stat_referrers stat_locations stat_paths}.each do |t|
|
||||||
|
DB.drop_index t, :created_at
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
9
models/archive.rb
Normal file
9
models/archive.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class Archive < Sequel::Model
|
||||||
|
many_to_one :site
|
||||||
|
set_primary_key [:site_id, :ipfs_hash]
|
||||||
|
unrestrict_primary_key
|
||||||
|
|
||||||
|
def url
|
||||||
|
"https://#{ipfs_hash}.ipfs.neocities.org"
|
||||||
|
end
|
||||||
|
end
|
362
models/site.rb
362
models/site.rb
|
@ -36,6 +36,10 @@ class Site < Sequel::Model
|
||||||
html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff woff2 json geojson csv tsv mf ico pdf asc key pgp xml mid midi manifest otf webapp
|
html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff woff2 json geojson csv tsv mf ico pdf asc key pgp xml mid midi manifest otf webapp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VALID_EDITABLE_EXTENSIONS = %w{
|
||||||
|
html htm txt js css md manifest
|
||||||
|
}
|
||||||
|
|
||||||
MINIMUM_PASSWORD_LENGTH = 5
|
MINIMUM_PASSWORD_LENGTH = 5
|
||||||
BAD_USERNAME_REGEX = /[^\w-]/i
|
BAD_USERNAME_REGEX = /[^\w-]/i
|
||||||
VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123
|
VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123
|
||||||
|
@ -73,7 +77,7 @@ class Site < Sequel::Model
|
||||||
PHISHING_FORM_REGEX = /www.formbuddy.com\/cgi-bin\/form.pl/i
|
PHISHING_FORM_REGEX = /www.formbuddy.com\/cgi-bin\/form.pl/i
|
||||||
SPAM_MATCH_REGEX = ENV['RACK_ENV'] == 'test' ? /pillz/ : /#{$config['spam_smart_filter'].join('|')}/i
|
SPAM_MATCH_REGEX = ENV['RACK_ENV'] == 'test' ? /pillz/ : /#{$config['spam_smart_filter'].join('|')}/i
|
||||||
EMAIL_SANITY_REGEX = /.+@.+\..+/i
|
EMAIL_SANITY_REGEX = /.+@.+\..+/i
|
||||||
EDITABLE_FILE_EXT = /html|htm|txt|js|css|md|manifest/i
|
EDITABLE_FILE_EXT = /#{VALID_EDITABLE_EXTENSIONS.join('|')}/i
|
||||||
BANNED_TIME = 2592000 # 30 days in seconds
|
BANNED_TIME = 2592000 # 30 days in seconds
|
||||||
TITLE_MAX = 100
|
TITLE_MAX = 100
|
||||||
|
|
||||||
|
@ -98,20 +102,35 @@ class Site < Sequel::Model
|
||||||
unlimited_site_creation: true,
|
unlimited_site_creation: true,
|
||||||
custom_ssl_certificates: true,
|
custom_ssl_certificates: true,
|
||||||
no_file_restrictions: true,
|
no_file_restrictions: true,
|
||||||
custom_domains: true
|
custom_domains: true,
|
||||||
|
maximum_site_files: 25000
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAN_FEATURES[:free] = PLAN_FEATURES[:supporter].merge(
|
PLAN_FEATURES[:free] = PLAN_FEATURES[:supporter].merge(
|
||||||
name: 'Free',
|
name: 'Free',
|
||||||
space: Filesize.from('50MB').to_i,
|
space: Filesize.from('100MB').to_i,
|
||||||
bandwidth: Filesize.from('50GB').to_i,
|
bandwidth: Filesize.from('50GB').to_i,
|
||||||
price: 0,
|
price: 0,
|
||||||
unlimited_site_creation: false,
|
unlimited_site_creation: false,
|
||||||
custom_ssl_certificates: false,
|
custom_ssl_certificates: false,
|
||||||
no_file_restrictions: false,
|
no_file_restrictions: false,
|
||||||
custom_domains: false
|
custom_domains: false,
|
||||||
|
maximum_site_files: 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def self.newsletter_sites
|
||||||
|
Site.select(:email).
|
||||||
|
exclude(email: 'nil').exclude(is_banned: true).
|
||||||
|
where{updated_at > EMAIL_BLAST_MAXIMUM_AGE}.
|
||||||
|
where{changed_count > 0}.
|
||||||
|
order(:updated_at.desc).
|
||||||
|
all
|
||||||
|
end
|
||||||
|
|
||||||
|
def too_many_files?(file_count=0)
|
||||||
|
(site_files_dataset.count + file_count) > plan_feature(:maximum_site_files)
|
||||||
|
end
|
||||||
|
|
||||||
def plan_feature(key)
|
def plan_feature(key)
|
||||||
PLAN_FEATURES[plan_type.to_sym][key.to_sym]
|
PLAN_FEATURES[plan_type.to_sym][key.to_sym]
|
||||||
end
|
end
|
||||||
|
@ -128,7 +147,15 @@ class Site < Sequel::Model
|
||||||
plan_five: 5
|
plan_five: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
BROWSE_PAGINATION_LENGTH = 300
|
BROWSE_PAGINATION_LENGTH = 100
|
||||||
|
|
||||||
|
EMAIL_BLAST_MAXIMUM_AGE = 6.months.ago
|
||||||
|
|
||||||
|
if ENV['RACK_ENV'] == 'test'
|
||||||
|
EMAIL_BLAST_MAXIMUM_PER_DAY = 2
|
||||||
|
else
|
||||||
|
EMAIL_BLAST_MAXIMUM_PER_DAY = 1000
|
||||||
|
end
|
||||||
|
|
||||||
many_to_many :tags
|
many_to_many :tags
|
||||||
|
|
||||||
|
@ -161,6 +188,13 @@ class Site < Sequel::Model
|
||||||
|
|
||||||
one_to_many :site_files
|
one_to_many :site_files
|
||||||
|
|
||||||
|
one_to_many :stats
|
||||||
|
one_to_many :stat_referrers
|
||||||
|
one_to_many :stat_locations
|
||||||
|
one_to_many :stat_paths
|
||||||
|
|
||||||
|
one_to_many :archives
|
||||||
|
|
||||||
def account_sites_dataset
|
def account_sites_dataset
|
||||||
Site.where(Sequel.|({id: owner.id}, {parent_site_id: owner.id})).order(:parent_site_id.desc, :username)
|
Site.where(Sequel.|({id: owner.id}, {parent_site_id: owner.id})).order(:parent_site_id.desc, :username)
|
||||||
end
|
end
|
||||||
|
@ -260,6 +294,7 @@ class Site < Sequel::Model
|
||||||
end
|
end
|
||||||
|
|
||||||
def banned_ip?(ip)
|
def banned_ip?(ip)
|
||||||
|
return false if ENV['RACK_ENV'] == 'production' && ip == '127.0.0.1'
|
||||||
return true if Site.where(is_banned: true).
|
return true if Site.where(is_banned: true).
|
||||||
where(ip: hash_ip(ip)).
|
where(ip: hash_ip(ip)).
|
||||||
where(['updated_at > ?', Time.now-BANNED_TIME]).
|
where(['updated_at > ?', Time.now-BANNED_TIME]).
|
||||||
|
@ -360,25 +395,26 @@ class Site < Sequel::Model
|
||||||
def install_new_files
|
def install_new_files
|
||||||
FileUtils.mkdir_p files_path
|
FileUtils.mkdir_p files_path
|
||||||
|
|
||||||
|
files = []
|
||||||
|
|
||||||
%w{index not_found}.each do |name|
|
%w{index not_found}.each do |name|
|
||||||
tmpfile = Tempfile.new "newinstall-#{name}"
|
tmpfile = Tempfile.new "newinstall-#{name}"
|
||||||
tmpfile.write render_template("#{name}.erb")
|
tmpfile.write render_template("#{name}.erb")
|
||||||
tmpfile.close
|
tmpfile.close
|
||||||
|
files << {filename: "#{name}.html", tempfile: tmpfile}
|
||||||
store_file "#{name}.html", tmpfile, new_install: true
|
|
||||||
purge_cache "/#{name}.html"
|
|
||||||
ScreenshotWorker.perform_async values[:username], "#{name}.html"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
tmpfile = Tempfile.new 'style.css'
|
tmpfile = Tempfile.new 'style.css'
|
||||||
tmpfile.close
|
tmpfile.close
|
||||||
FileUtils.cp template_file_path('style.css'), tmpfile.path
|
FileUtils.cp template_file_path('style.css'), tmpfile.path
|
||||||
store_file 'style.css', tmpfile, new_install: true
|
files << {filename: 'style.css', tempfile: tmpfile}
|
||||||
|
|
||||||
tmpfile = Tempfile.new 'cat.png'
|
tmpfile = Tempfile.new 'cat.png'
|
||||||
tmpfile.close
|
tmpfile.close
|
||||||
FileUtils.cp template_file_path('cat.png'), tmpfile.path
|
FileUtils.cp template_file_path('cat.png'), tmpfile.path
|
||||||
store_file 'cat.png', tmpfile, new_install: true
|
files << {filename: 'cat.png', tempfile: tmpfile}
|
||||||
|
|
||||||
|
store_files files, new_install: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_file(path)
|
def get_file(path)
|
||||||
|
@ -539,77 +575,57 @@ class Site < Sequel::Model
|
||||||
end
|
end
|
||||||
|
|
||||||
def purge_cache(path)
|
def purge_cache(path)
|
||||||
relative_path = path.gsub(base_files_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(path, uploaded, opts={})
|
# We gotta flush the dirname too if it's an index file.
|
||||||
relative_path = scrubbed_path path
|
if relative_path != '' && relative_path.match(/\/$|index\.html?$/i)
|
||||||
path = files_path path
|
PurgeCacheOrderWorker.perform_async username, relative_path
|
||||||
pathname = Pathname(path)
|
PurgeCacheOrderWorker.perform_async username, Pathname(relative_path).dirname.to_s
|
||||||
|
|
||||||
site_file = site_files_dataset.where(path: relative_path).first
|
|
||||||
|
|
||||||
uploaded_sha1 = Digest::SHA1.file(uploaded.path).hexdigest
|
|
||||||
|
|
||||||
if site_file && site_file.sha1_hash == uploaded_sha1
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if relative_path == 'index.html' && opts[:new_install] != true
|
|
||||||
begin
|
|
||||||
new_title = Nokogiri::HTML(File.read(uploaded.path)).css('title').first.text
|
|
||||||
rescue NoMethodError => e
|
|
||||||
else
|
else
|
||||||
if new_title.length < TITLE_MAX
|
PurgeCacheOrderWorker.perform_async username, relative_path
|
||||||
self.title = new_title
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.site_changed = true
|
Rye::Cmd.add_command :ipfs, nil, 'add', :r
|
||||||
self.site_updated_at = Time.now
|
|
||||||
self.updated_at = Time.now
|
|
||||||
|
|
||||||
save_changes(validate: false)
|
def add_to_ipfs
|
||||||
|
# Not ideal. An SoA version is in progress.
|
||||||
|
if $config['ipfs_ssh_host'] && $config['ipfs_ssh_user']
|
||||||
|
rbox = Rye::Box.new $config['ipfs_ssh_host'], :user => $config['ipfs_ssh_user']
|
||||||
|
begin
|
||||||
|
response = rbox.ipfs "sites/#{self.username.gsub(/\/|\.\./, '')}"
|
||||||
|
output_array = response
|
||||||
|
ensure
|
||||||
|
rbox.disconnect
|
||||||
|
end
|
||||||
|
else
|
||||||
|
line = Cocaine::CommandLine.new('ipfs', 'add -r :path')
|
||||||
|
response = line.run path: files_path
|
||||||
|
output_array = response.to_s.split("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
if pathname.extname.match HTML_REGEX
|
output_array.last.split(' ')[1]
|
||||||
# SPAM and phishing checking code goes here
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dirname = pathname.dirname.to_s
|
def archive!
|
||||||
|
#if ENV["RACK_ENV"] == 'test'
|
||||||
|
# ipfs_hash = "QmcKi2ae3uGb1kBg1yBpsuwoVqfmcByNdMiZ2pukxyLWD8"
|
||||||
|
#else
|
||||||
|
#end
|
||||||
|
|
||||||
if !File.exists? dirname
|
ipfs_hash = add_to_ipfs
|
||||||
FileUtils.mkdir_p dirname
|
|
||||||
|
archive = archives_dataset.where(ipfs_hash: ipfs_hash).first
|
||||||
|
if archive
|
||||||
|
archive.updated_at = Time.now
|
||||||
|
archive.save_changes
|
||||||
|
else
|
||||||
|
add_archive ipfs_hash: ipfs_hash, updated_at: Time.now
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
uploaded_size = uploaded.size
|
def latest_archive
|
||||||
|
@latest_archive ||= archives_dataset.order(:updated_at.desc).first
|
||||||
FileUtils.cp uploaded.path, path
|
|
||||||
File.chmod 0640, path
|
|
||||||
|
|
||||||
site_file ||= SiteFile.new site_id: self.id, path: relative_path
|
|
||||||
|
|
||||||
site_file.set_all(
|
|
||||||
size: uploaded_size,
|
|
||||||
sha1_hash: uploaded_sha1,
|
|
||||||
updated_at: Time.now
|
|
||||||
)
|
|
||||||
site_file.save
|
|
||||||
|
|
||||||
purge_cache path
|
|
||||||
|
|
||||||
if pathname.extname.match HTML_REGEX
|
|
||||||
ScreenshotWorker.perform_async values[:username], relative_path
|
|
||||||
elsif pathname.extname.match IMAGE_REGEX
|
|
||||||
ThumbnailWorker.perform_async values[:username], relative_path
|
|
||||||
end
|
|
||||||
|
|
||||||
SiteChange.record self, relative_path unless opts[:new_install]
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_directory?(path)
|
def is_directory?(path)
|
||||||
|
@ -626,11 +642,6 @@ class Site < Sequel::Model
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def increment_changed_count
|
|
||||||
self.changed_count += 1
|
|
||||||
save_changes(validate: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
def files_zip
|
def files_zip
|
||||||
zip_name = "neocities-#{username}"
|
zip_name = "neocities-#{username}"
|
||||||
|
|
||||||
|
@ -654,41 +665,17 @@ class Site < Sequel::Model
|
||||||
tmpfile.path
|
tmpfile.path
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_file(path)
|
|
||||||
begin
|
|
||||||
FileUtils.rm files_path(path)
|
|
||||||
rescue Errno::EISDIR
|
|
||||||
site_files.each do |site_file|
|
|
||||||
if site_file.path.match /^#{path}\//
|
|
||||||
site_file.destroy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
FileUtils.remove_dir files_path(path), true
|
|
||||||
rescue Errno::ENOENT
|
|
||||||
end
|
|
||||||
|
|
||||||
purge_cache path
|
|
||||||
|
|
||||||
ext = File.extname(path).gsub(/^./, '')
|
|
||||||
|
|
||||||
screenshots_delete(path) if ext.match HTML_REGEX
|
|
||||||
thumbnails_delete(path) if ext.match IMAGE_REGEX
|
|
||||||
|
|
||||||
path = path[1..path.length] if path[0] == '/'
|
|
||||||
|
|
||||||
site_files_dataset.where(path: path).delete
|
|
||||||
SiteChangeFile.filter(site_id: self.id, filename: path).delete
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def move_files_from(oldusername)
|
def move_files_from(oldusername)
|
||||||
FileUtils.mv base_files_path(oldusername), base_files_path
|
FileUtils.mv base_files_path(oldusername), base_files_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_new_html_file(path)
|
def install_new_html_file(path)
|
||||||
File.write files_path(path), render_template('index.erb')
|
tmpfile = Tempfile.new 'neocities_html_template'
|
||||||
|
tmpfile.write render_template('index.erb')
|
||||||
|
tmpfile.close
|
||||||
|
store_files [{filename: path, tempfile: tmpfile}]
|
||||||
purge_cache path
|
purge_cache path
|
||||||
|
tmpfile.unlink
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_exists?(path)
|
def file_exists?(path)
|
||||||
|
@ -830,6 +817,18 @@ class Site < Sequel::Model
|
||||||
new_tags.compact!
|
new_tags.compact!
|
||||||
@new_filtered_tags = []
|
@new_filtered_tags = []
|
||||||
|
|
||||||
|
if values[:is_education] == true
|
||||||
|
if new?
|
||||||
|
if @new_tags_string.nil? || @new_tags_string.empty?
|
||||||
|
errors.add :new_tags_string, 'A Class Tag is required.'
|
||||||
|
end
|
||||||
|
|
||||||
|
if new_tags.length > 1
|
||||||
|
errors.add :new_tags_string, 'Must only have one tag'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if ((new? ? 0 : tags_dataset.count) + new_tags.length > 5)
|
if ((new? ? 0 : tags_dataset.count) + new_tags.length > 5)
|
||||||
errors.add :new_tags_string, 'Cannot have more than 5 tags for your site.'
|
errors.add :new_tags_string, 'Cannot have more than 5 tags for your site.'
|
||||||
end
|
end
|
||||||
|
@ -856,7 +855,7 @@ class Site < Sequel::Model
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
next if tags.collect {|t| t.name}.include? tag
|
next if !new? && tags.collect {|t| t.name}.include?(tag)
|
||||||
|
|
||||||
@new_filtered_tags << tag
|
@new_filtered_tags << tag
|
||||||
@new_filtered_tags.uniq!
|
@new_filtered_tags.uniq!
|
||||||
|
@ -906,9 +905,17 @@ class Site < Sequel::Model
|
||||||
is_root_index: file_path == "#{base_files_path}/index.html"
|
is_root_index: file_path == "#{base_files_path}/index.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
site_file = site_files_dataset.where(path: file_path.gsub(base_files_path, '').sub(/^\//, '')).first
|
||||||
|
|
||||||
|
if site_file
|
||||||
|
file[:size] = site_file.size unless file[:is_directory]
|
||||||
|
file[:updated_at] = site_file.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
file[:is_html] = !(file[:ext].match HTML_REGEX).nil?
|
file[:is_html] = !(file[:ext].match HTML_REGEX).nil?
|
||||||
file[:is_image] = !(file[:ext].match IMAGE_REGEX).nil?
|
file[:is_image] = !(file[:ext].match IMAGE_REGEX).nil?
|
||||||
file[:is_editable] = !(file[:ext].match EDITABLE_FILE_EXT).nil?
|
file[:is_editable] = !(file[:ext].match EDITABLE_FILE_EXT).nil?
|
||||||
|
|
||||||
file
|
file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -945,6 +952,7 @@ class Site < Sequel::Model
|
||||||
((total_space_used.to_f / maximum_space) * 100).round(1)
|
((total_space_used.to_f / maximum_space) * 100).round(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Note: Change Stat#prune! if you change this business logic.
|
||||||
def supporter?
|
def supporter?
|
||||||
owner.plan_type != 'free'
|
owner.plan_type != 'free'
|
||||||
end
|
end
|
||||||
|
@ -966,6 +974,7 @@ class Site < Sequel::Model
|
||||||
!values[:plan_type].match(/plan_/).nil?
|
!values[:plan_type].match(/plan_/).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Note: Change Stat#prune! if you change this business logic.
|
||||||
def plan_type
|
def plan_type
|
||||||
return 'free' if owner.values[:plan_type].nil?
|
return 'free' if owner.values[:plan_type].nil?
|
||||||
return 'supporter' if owner.values[:plan_type].match /^plan_/
|
return 'supporter' if owner.values[:plan_type].match /^plan_/
|
||||||
|
@ -1089,4 +1098,149 @@ class Site < Sequel::Model
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# array of hashes: filename, tempfile, opts.
|
||||||
|
def store_files(files, opts={})
|
||||||
|
results = []
|
||||||
|
new_size = 0
|
||||||
|
html_uploaded = false
|
||||||
|
|
||||||
|
if too_many_files?(files.length)
|
||||||
|
results << false
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
files.each do |file|
|
||||||
|
html_uploaded = true if file[:filename].match HTML_REGEX
|
||||||
|
|
||||||
|
existing_size = 0
|
||||||
|
site_file = site_files_dataset.where(path: scrubbed_path(file[:filename])).first
|
||||||
|
if site_file
|
||||||
|
existing_size = site_file.size
|
||||||
|
end
|
||||||
|
|
||||||
|
res = store_file(file[:filename], file[:tempfile], file[:opts] || opts)
|
||||||
|
if res == true
|
||||||
|
new_size -= existing_size
|
||||||
|
new_size += file[:tempfile].size
|
||||||
|
end
|
||||||
|
results << res
|
||||||
|
end
|
||||||
|
|
||||||
|
if results.include? true && opts[:new_install] != true
|
||||||
|
time = Time.now
|
||||||
|
sql = DB["update sites set site_changed=?, site_updated_at=?, updated_at=?, changed_count=changed_count+1, space_used=space_used#{new_size < 0 ? new_size.to_s : '+'+new_size.to_s} where id=?",
|
||||||
|
true,
|
||||||
|
time,
|
||||||
|
time,
|
||||||
|
self.id
|
||||||
|
]
|
||||||
|
sql.first
|
||||||
|
reload
|
||||||
|
|
||||||
|
#SiteChange.record self, relative_path unless opts[:new_install]
|
||||||
|
ArchiveWorker.perform_async self.id
|
||||||
|
end
|
||||||
|
|
||||||
|
results
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_file(path)
|
||||||
|
return false if files_path(path) == files_path
|
||||||
|
begin
|
||||||
|
FileUtils.rm files_path(path)
|
||||||
|
rescue Errno::EISDIR
|
||||||
|
site_files.each do |site_file|
|
||||||
|
if site_file.path.match /^#{path}\//
|
||||||
|
site_file.destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
FileUtils.remove_dir files_path(path), true
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
end
|
||||||
|
|
||||||
|
purge_cache path
|
||||||
|
|
||||||
|
ext = File.extname(path).gsub(/^./, '')
|
||||||
|
|
||||||
|
screenshots_delete(path) if ext.match HTML_REGEX
|
||||||
|
thumbnails_delete(path) if ext.match IMAGE_REGEX
|
||||||
|
|
||||||
|
path = path[1..path.length] if path[0] == '/'
|
||||||
|
|
||||||
|
DB.transaction do
|
||||||
|
site_file = site_files_dataset.where(path: path).first
|
||||||
|
if site_file
|
||||||
|
DB['update sites set space_used=space_used-? where id=?', site_file.size, self.id].first
|
||||||
|
site_file.delete
|
||||||
|
end
|
||||||
|
SiteChangeFile.filter(site_id: self.id, filename: path).delete
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def store_file(path, uploaded, opts={})
|
||||||
|
relative_path = scrubbed_path path
|
||||||
|
path = files_path path
|
||||||
|
pathname = Pathname(path)
|
||||||
|
|
||||||
|
site_file = site_files_dataset.where(path: relative_path).first
|
||||||
|
|
||||||
|
uploaded_sha1 = Digest::SHA1.file(uploaded.path).hexdigest
|
||||||
|
|
||||||
|
if site_file && site_file.sha1_hash == uploaded_sha1
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if relative_path == 'index.html'
|
||||||
|
begin
|
||||||
|
new_title = Nokogiri::HTML(File.read(uploaded.path)).css('title').first.text
|
||||||
|
rescue NoMethodError => e
|
||||||
|
else
|
||||||
|
if new_title.length < TITLE_MAX
|
||||||
|
self.title = new_title
|
||||||
|
save_changes validate: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if pathname.extname.match HTML_REGEX
|
||||||
|
# SPAM and phishing checking code goes here
|
||||||
|
end
|
||||||
|
|
||||||
|
dirname = pathname.dirname.to_s
|
||||||
|
|
||||||
|
if !File.exists? dirname
|
||||||
|
FileUtils.mkdir_p dirname
|
||||||
|
end
|
||||||
|
|
||||||
|
uploaded_size = uploaded.size
|
||||||
|
|
||||||
|
FileUtils.cp uploaded.path, path
|
||||||
|
File.chmod 0640, path
|
||||||
|
|
||||||
|
SiteChange.record self, relative_path unless opts[:new_install]
|
||||||
|
|
||||||
|
site_file ||= SiteFile.new site_id: self.id, path: relative_path
|
||||||
|
|
||||||
|
site_file.set_all(
|
||||||
|
size: uploaded_size,
|
||||||
|
sha1_hash: uploaded_sha1,
|
||||||
|
updated_at: Time.now
|
||||||
|
)
|
||||||
|
site_file.save
|
||||||
|
|
||||||
|
purge_cache path
|
||||||
|
|
||||||
|
if pathname.extname.match HTML_REGEX
|
||||||
|
ScreenshotWorker.perform_async values[:username], relative_path
|
||||||
|
elsif pathname.extname.match IMAGE_REGEX
|
||||||
|
ThumbnailWorker.perform_async values[:username], relative_path
|
||||||
|
end
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
class SiteFile < Sequel::Model
|
class SiteFile < Sequel::Model
|
||||||
|
|
||||||
unrestrict_primary_key
|
unrestrict_primary_key
|
||||||
plugin :update_primary_key
|
plugin :update_primary_key
|
||||||
many_to_one :site
|
many_to_one :site
|
||||||
|
|
241
models/stat.rb
241
models/stat.rb
|
@ -1,3 +1,244 @@
|
||||||
class Stat < Sequel::Model
|
class Stat < Sequel::Model
|
||||||
|
FREE_RETAINMENT_DAYS = 30
|
||||||
|
|
||||||
many_to_one :site
|
many_to_one :site
|
||||||
|
one_to_many :stat_referrers
|
||||||
|
one_to_many :stat_locations
|
||||||
|
one_to_many :stat_paths
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def prune!
|
||||||
|
DB[
|
||||||
|
"DELETE FROM stats WHERE created_at < ? AND site_id NOT IN (SELECT id FROM sites WHERE plan_type IS NOT NULL OR plan_type != 'free')",
|
||||||
|
(FREE_RETAINMENT_DAYS-1).days.ago.to_date.to_s
|
||||||
|
].first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_logfiles(path)
|
||||||
|
Dir["#{path}/*.log"].each do |log_path|
|
||||||
|
site_logs = {}
|
||||||
|
logfile = File.open log_path, 'r'
|
||||||
|
|
||||||
|
while hit = logfile.gets
|
||||||
|
hit_array = hit.strip.split "\t"
|
||||||
|
|
||||||
|
raise ArgumentError, hit.inspect if hit_array.length > 6
|
||||||
|
|
||||||
|
time, username, size, path, ip, referrer = hit_array
|
||||||
|
|
||||||
|
next if !referrer.nil? && referrer.match(/bot/i)
|
||||||
|
|
||||||
|
site_logs[username] = {
|
||||||
|
hits: 0,
|
||||||
|
views: 0,
|
||||||
|
bandwidth: 0,
|
||||||
|
view_ips: [],
|
||||||
|
ips: [],
|
||||||
|
referrers: {},
|
||||||
|
paths: {}
|
||||||
|
} unless site_logs[username]
|
||||||
|
|
||||||
|
site_logs[username][:hits] += 1
|
||||||
|
site_logs[username][:bandwidth] += size.to_i
|
||||||
|
|
||||||
|
unless site_logs[username][:view_ips].include?(ip)
|
||||||
|
site_logs[username][:views] += 1
|
||||||
|
site_logs[username][:view_ips] << ip
|
||||||
|
|
||||||
|
if referrer != '-' && !referrer.nil?
|
||||||
|
site_logs[username][:referrers][referrer] ||= 0
|
||||||
|
site_logs[username][:referrers][referrer] += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
site_logs[username][:paths][path] ||= 0
|
||||||
|
site_logs[username][:paths][path] += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
logfile.close
|
||||||
|
|
||||||
|
current_time = Time.now.utc
|
||||||
|
current_day_string = current_time.to_date.to_s
|
||||||
|
|
||||||
|
Site.select(:id, :username).where(username: site_logs.keys).all.each do |site|
|
||||||
|
site_logs[site.username][:id] = site.id
|
||||||
|
end
|
||||||
|
|
||||||
|
DB.transaction do
|
||||||
|
site_logs.each do |username, site_log|
|
||||||
|
DB['update sites set hits=hits+?, views=views+? where username=?',
|
||||||
|
site_log[:hits],
|
||||||
|
site_log[:views],
|
||||||
|
username
|
||||||
|
].first
|
||||||
|
|
||||||
|
opts = {site_id: site_log[:id], created_at: current_day_string}
|
||||||
|
|
||||||
|
stat = Stat.select(:id).where(opts).first
|
||||||
|
DB[:stats].lock('EXCLUSIVE') { stat = Stat.create opts } if stat.nil?
|
||||||
|
|
||||||
|
DB[
|
||||||
|
'update stats set hits=hits+?, views=views+?, bandwidth=bandwidth+? where id=?',
|
||||||
|
site_log[:hits],
|
||||||
|
site_log[:views],
|
||||||
|
site_log[:bandwidth],
|
||||||
|
stat.id
|
||||||
|
].first
|
||||||
|
|
||||||
|
=begin
|
||||||
|
site_log[:referrers].each do |referrer, views|
|
||||||
|
stat_referrer = StatReferrer.create_or_get site_log[:id], referrer
|
||||||
|
DB['update stat_referrers set views=views+? where site_id=?', views, site_log[:id]].first
|
||||||
|
end
|
||||||
|
|
||||||
|
site_log[:view_ips].each do |ip|
|
||||||
|
site_location = StatLocation.create_or_get site_log[:id], ip
|
||||||
|
next if site_location.nil?
|
||||||
|
DB['update stat_locations set views=views+1 where id=?', site_location.id].first
|
||||||
|
end
|
||||||
|
|
||||||
|
site_log[:paths].each do |path, views|
|
||||||
|
site_path = StatPath.create_or_get site_log[:id], path
|
||||||
|
next if site_path.nil?
|
||||||
|
DB['update stat_paths set views=views+? where id=?', views, site_path.id].first
|
||||||
|
end
|
||||||
|
=end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
FileUtils.rm log_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
require 'io/extra'
|
||||||
|
require 'geoip'
|
||||||
|
|
||||||
|
# Note: This isn't really a class right now.
|
||||||
|
module Stat
|
||||||
|
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def parse_logfiles(path)
|
||||||
|
Dir["#{path}/*.log"].each do |logfile_path|
|
||||||
|
parse_logfile logfile_path
|
||||||
|
FileUtils.rm logfile_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_logfile(path)
|
||||||
|
geoip = GeoIP.new GEOCITY_PATH
|
||||||
|
logfile = File.open path, 'r'
|
||||||
|
|
||||||
|
hits = []
|
||||||
|
|
||||||
|
while hit = logfile.gets
|
||||||
|
time, username, size, path, ip, referrer = hit.split ' '
|
||||||
|
|
||||||
|
site = Site.select(:id).where(username: username).first
|
||||||
|
next unless site
|
||||||
|
|
||||||
|
paths_dataset = StatsDB[:paths]
|
||||||
|
path_record = paths_dataset[name: path]
|
||||||
|
path_id = path_record ? path_record[:id] : paths_dataset.insert(name: path)
|
||||||
|
|
||||||
|
referrers_dataset = StatsDB[:referrers]
|
||||||
|
referrer_record = referrers_dataset[name: referrer]
|
||||||
|
referrer_id = referrer_record ? referrer_record[:id] : referrers_dataset.insert(name: referrer)
|
||||||
|
|
||||||
|
location_id = nil
|
||||||
|
|
||||||
|
if city = geoip.city(ip)
|
||||||
|
locations_dataset = StatsDB[:locations].select(:id)
|
||||||
|
location_hash = {country_code2: city.country_code2, region_name: city.region_name, city_name: city.city_name}
|
||||||
|
|
||||||
|
location = locations_dataset.where(location_hash).first
|
||||||
|
location_id = location ? location[:id] : locations_dataset.insert(location_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
hits << [site.id, referrer_id, path_id, location_id, size, time]
|
||||||
|
end
|
||||||
|
|
||||||
|
StatsDB[:hits].import(
|
||||||
|
[:site_id, :referrer_id, :path_id, :location_id, :bytes_sent, :logged_at],
|
||||||
|
hits
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=begin
|
||||||
|
def parse_logfile(path)
|
||||||
|
hits = {}
|
||||||
|
visits = {}
|
||||||
|
visit_ips = {}
|
||||||
|
|
||||||
|
logfile = File.open path, 'r'
|
||||||
|
|
||||||
|
while hit = logfile.gets
|
||||||
|
time, username, size, path, ip, referrer = hit.split ' '
|
||||||
|
|
||||||
|
hits[username] ||= 0
|
||||||
|
hits[username] += 1
|
||||||
|
visit_ips[username] = [] if !visit_ips[username]
|
||||||
|
|
||||||
|
unless visit_ips[username].include? ip
|
||||||
|
visits[username] ||= 0
|
||||||
|
visits[username] += 1
|
||||||
|
visit_ips[username] << ip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
logfile.close
|
||||||
|
|
||||||
|
|
||||||
|
hits.each do |username,hitcount|
|
||||||
|
DB['update sites set hits=hits+? where username=?', hitcount, username].first
|
||||||
|
end
|
||||||
|
|
||||||
|
visits.each do |username,visitcount|
|
||||||
|
DB['update sites set views=views+? where username=?', visitcount, username].first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
=end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
def self.parse(logfile_path)
|
||||||
|
hits = {}
|
||||||
|
visits = {}
|
||||||
|
visit_ips = {}
|
||||||
|
|
||||||
|
logfile = File.open logfile_path, 'r'
|
||||||
|
|
||||||
|
while hit = logfile.gets
|
||||||
|
time, username, size, path, ip = hit.split ' '
|
||||||
|
|
||||||
|
hits[username] ||= 0
|
||||||
|
hits[username] += 1
|
||||||
|
|
||||||
|
visit_ips[username] = [] if !visit_ips[username]
|
||||||
|
|
||||||
|
unless visit_ips[username].include?(ip)
|
||||||
|
visits[username] ||= 0
|
||||||
|
visits[username] += 1
|
||||||
|
visit_ips[username] << ip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
logfile.close
|
||||||
|
|
||||||
|
hits.each do |username,hitcount|
|
||||||
|
DB['update sites set hits=hits+? where username=?', hitcount, username].first
|
||||||
|
end
|
||||||
|
|
||||||
|
visits.each do |username,visitcount|
|
||||||
|
DB['update sites set views=views+? where username=?', visitcount, username].first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
=end
|
||||||
|
|
27
models/stat_location.rb
Normal file
27
models/stat_location.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
require 'geoip'
|
||||||
|
|
||||||
|
class StatLocation < Sequel::Model
|
||||||
|
GEOCITY_PATH = './files/GeoLiteCity.dat'
|
||||||
|
RETAINMENT_DAYS = 7
|
||||||
|
|
||||||
|
many_to_one :site
|
||||||
|
|
||||||
|
def self.prune!
|
||||||
|
where{created_at < (RETAINMENT_DAYS-2).days.ago.to_date}.delete
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_or_get(site_id, ip)
|
||||||
|
geoip = GeoIP.new GEOCITY_PATH
|
||||||
|
city = geoip.city ip
|
||||||
|
|
||||||
|
return nil if city.nil?
|
||||||
|
|
||||||
|
opts = {site_id: site_id, country_code2: city.country_code2, region_name: city.region_name, city_name: city.city_name}
|
||||||
|
stat_location = where(opts).where{created_at > RETAINMENT_DAYS.days.ago}.first
|
||||||
|
DB[table_name].lock('EXCLUSIVE') {
|
||||||
|
stat_location = create opts.merge(latitude: city.latitude, longitude: city.longitude, created_at: Date.today)
|
||||||
|
} if stat_location.nil?
|
||||||
|
|
||||||
|
stat_location
|
||||||
|
end
|
||||||
|
end
|
19
models/stat_path.rb
Normal file
19
models/stat_path.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
class StatPath < Sequel::Model
|
||||||
|
RETAINMENT_DAYS = 7
|
||||||
|
|
||||||
|
many_to_one :site
|
||||||
|
|
||||||
|
def self.prune!
|
||||||
|
where{created_at < (RETAINMENT_DAYS-2).days.ago.to_date}.delete
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_or_get(site_id, name)
|
||||||
|
opts = {site_id: site_id, name: name}
|
||||||
|
stat_path = where(opts).where{created_at > RETAINMENT_DAYS.days.ago}.first
|
||||||
|
DB[table_name].lock('EXCLUSIVE') {
|
||||||
|
stat_path = create opts.merge created_at: Date.today
|
||||||
|
} if stat_path.nil?
|
||||||
|
|
||||||
|
stat_path
|
||||||
|
end
|
||||||
|
end
|
19
models/stat_referrer.rb
Normal file
19
models/stat_referrer.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
class StatReferrer < Sequel::Model
|
||||||
|
many_to_one :site
|
||||||
|
RETAINMENT_DAYS = 7
|
||||||
|
|
||||||
|
def self.prune!
|
||||||
|
where{created_at < (RETAINMENT_DAYS-2).days.ago.to_date}.delete
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_or_get(site_id, url)
|
||||||
|
opts = {site_id: site_id, url: url}
|
||||||
|
stat_referrer = where(opts).where{created_at > RETAINMENT_DAYS.days.ago}.first
|
||||||
|
|
||||||
|
DB[table_name].lock('EXCLUSIVE') {
|
||||||
|
stat_referrer = create opts.merge(created_at: Date.today)
|
||||||
|
} if stat_referrer.nil?
|
||||||
|
|
||||||
|
stat_referrer
|
||||||
|
end
|
||||||
|
end
|
BIN
public/img/kickstarterlogo.png
Normal file
BIN
public/img/kickstarterlogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/img/kickstarterthumbnail.jpg
Normal file
BIN
public/img/kickstarterthumbnail.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
BIN
public/img/neocities-ipfs-large.png
Normal file
BIN
public/img/neocities-ipfs-large.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 484 KiB |
BIN
public/img/neocities-ipfs.jpg
Normal file
BIN
public/img/neocities-ipfs.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
public/img/neocities-logo-education.png
Normal file
BIN
public/img/neocities-logo-education.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
BIN
public/img/tutorialthumbnail.png
Normal file
BIN
public/img/tutorialthumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
11
public/js/Chart.min.js
vendored
Normal file
11
public/js/Chart.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -3,16 +3,13 @@ var Comment = {
|
||||||
var form = $(form)
|
var form = $(form)
|
||||||
var comment = form.find('[name="comment"]').val()
|
var comment = form.find('[name="comment"]').val()
|
||||||
form.remove()
|
form.remove()
|
||||||
|
|
||||||
$.post('/event/'+eventId+'/comment', {csrf_token: csrfToken, message: comment}, function(res) {
|
$.post('/event/'+eventId+'/comment', {csrf_token: csrfToken, message: comment}, function(res) {
|
||||||
console.log(res)
|
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: function(commentId, csrfToken) {
|
delete: function(commentId, csrfToken) {
|
||||||
$.post('/comment/'+commentId+'/delete', {csrf_token: csrfToken}, function(res) {
|
$.post('/comment/'+commentId+'/delete', {csrf_token: csrfToken}, function(res) {
|
||||||
console.log(res)
|
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -59,7 +59,7 @@ a{
|
||||||
@import 'project-Main'; // Project Specific Main Content Area Styling
|
@import 'project-Main'; // Project Specific Main Content Area Styling
|
||||||
@import 'project-Footer'; // Project Specific Footer Styling
|
@import 'project-Footer'; // Project Specific Footer Styling
|
||||||
|
|
||||||
.alert{
|
.alert-error {
|
||||||
background-color:#F5BA00; color:#fff;
|
background-color:#F5BA00; color:#fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hp.education {
|
||||||
|
.col.intro {
|
||||||
|
padding-top: 20px;
|
||||||
|
img {
|
||||||
|
@include vendor(transform, scaleX(-1));
|
||||||
|
width: 100px;
|
||||||
|
margin-right: 25px;
|
||||||
|
|
||||||
|
&.float-Right {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.intro-text {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.intro-List{
|
.intro-List{
|
||||||
@extend %kill-List;
|
@extend %kill-List;
|
||||||
|
@ -490,4 +507,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.interior .constant-Nav{margin:0}
|
.interior .constant-Nav, .hp.education .constant-Nav{margin:0}
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
.interior .header-Outro h1 {
|
.interior .header-Outro h1 {
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.news-site-info {
|
.news-site-info {
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
@ -127,15 +131,30 @@
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 7px 15px;
|
padding: 7px 15px;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
|
||||||
margin: 6px 0px 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.row.content {
|
.row.content {
|
||||||
margin-left: 6%;
|
margin-left: 6%;
|
||||||
margin-right: 6%;
|
margin-right: 6%;
|
||||||
}
|
}
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
.signup-Area {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.row.content.wide {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 13px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.btn-Action {
|
||||||
|
margin: 6px 4px 8px;
|
||||||
|
}
|
||||||
|
.site-url {
|
||||||
|
margin-top: -13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.interior .header-Outro a {color:#E93250}
|
.interior .header-Outro a {color:#E93250}
|
||||||
.interior .header-Outro .btn-Action {color:#fff}
|
.interior .header-Outro .btn-Action {color:#fff}
|
||||||
|
@ -205,8 +224,8 @@
|
||||||
float: left;
|
float: left;
|
||||||
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) {
|
@media (max-device-width: 480px), screen and (max-width: 800px) {
|
||||||
margin-left: 22px;
|
margin-left: 0px;
|
||||||
margin-top: 18px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.welcome {
|
.welcome {
|
||||||
|
@ -233,10 +252,55 @@
|
||||||
}
|
}
|
||||||
.files {
|
.files {
|
||||||
float:left;
|
float:left;
|
||||||
background: #E4D8CB;
|
background: #EAE1D5;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 7px;
|
margin-top: 7px;
|
||||||
|
|
||||||
|
.column, input[type='checkbox'] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
float: left;
|
||||||
|
margin-right: 15px;
|
||||||
|
margin-left: -3px;
|
||||||
|
|
||||||
|
>.btn+.btn {
|
||||||
|
margin-left: 0px;
|
||||||
|
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
padding: 7px 11px;
|
||||||
|
margin-top: 1px;
|
||||||
|
background: #77ABB8;
|
||||||
|
@include box-shadow(0 0 5px rgba(0, 0, 0, 0.2));
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #83B3C0;
|
||||||
|
}
|
||||||
|
&:focus, &.active {
|
||||||
|
outline: 0;
|
||||||
|
background: #4F727B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.files {
|
||||||
|
.btn.iconview-button {
|
||||||
|
background: #4F727B;
|
||||||
|
}
|
||||||
|
.btn.listview-button {
|
||||||
|
background: #77ABB8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.files.list-view {
|
||||||
|
.btn.listview-button {
|
||||||
|
background: #4F727B;
|
||||||
|
}
|
||||||
|
.btn.iconview-button {
|
||||||
|
background: #77ABB8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.files .header {
|
.files .header {
|
||||||
background: #5E95A1;
|
background: #5E95A1;
|
||||||
|
@ -259,12 +323,22 @@
|
||||||
}
|
}
|
||||||
.files .actions {
|
.files .actions {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
float: left;
|
||||||
|
margin-top: 7px;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.files .btn-Action {
|
.files .btn-Action {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
margin: 4px 8px 4px 0;
|
margin: 4px 3px 4px 0;
|
||||||
|
padding: 8px 17px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.files .list {
|
.files .list {
|
||||||
|
@ -278,7 +352,7 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
-webkit-border-radius: 8px;
|
-webkit-border-radius: 8px;
|
||||||
-moz-border-radius: 8px;
|
-moz-border-radius: 8px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
.files .list .upload-Boundary.with-instruction {
|
.files .list .upload-Boundary.with-instruction {
|
||||||
|
@ -360,6 +434,7 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.html-thumbnail {
|
.html-thumbnail {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
@ -448,6 +523,113 @@
|
||||||
.html-thumbnail.misc.fileimagehover .overlay {
|
.html-thumbnail.misc.fileimagehover .overlay {
|
||||||
margin: 1px 0 0 2px;
|
margin: 1px 0 0 2px;
|
||||||
}
|
}
|
||||||
|
@mixin dashboard-list-view {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.upload-Boundary {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.file {
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: #EFE8DC;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 7px;
|
||||||
|
margin-top: 2px;
|
||||||
|
float: left;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
width: 30%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type='checkbox'] {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.html-thumbnail, .misc-icon {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
float: left;
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
background-size: 23px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 8px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 23px;
|
||||||
|
max-height: 23px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.misc-icon {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.folder-icon {
|
||||||
|
background-position: 0 4px;
|
||||||
|
background-size: 23px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
.file > .overlay {
|
||||||
|
padding-top: 11px;
|
||||||
|
margin-left: 20px;
|
||||||
|
text-align: right;
|
||||||
|
background-color: transparent;
|
||||||
|
display: block;
|
||||||
|
width: 94%;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #e93250;
|
||||||
|
display: inline;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.link-overlay {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
width: 84%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.html-thumbnail > .overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.column {
|
||||||
|
float: left;
|
||||||
|
width: 13%;
|
||||||
|
font-size: 13px;
|
||||||
|
display: block;
|
||||||
|
padding-top: 4px;
|
||||||
|
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
.files .list {
|
||||||
|
@include dashboard-list-view;
|
||||||
|
}
|
||||||
|
.files .btn-group {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.files.list-view .list {
|
||||||
|
@include dashboard-list-view;
|
||||||
|
}
|
||||||
.site-actions {
|
.site-actions {
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
@ -751,6 +933,16 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
.news-item.comment .icon {
|
.news-item.comment .icon {
|
||||||
background: #DAEEA5;
|
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 .icon-mini {
|
||||||
|
|
||||||
}
|
}
|
||||||
.news-item.update .icon {
|
.news-item.update .icon {
|
||||||
background: #E93250;
|
background: #E93250;
|
||||||
|
@ -1204,6 +1396,28 @@ a.tag:hover {
|
||||||
background-image:url(/img/hpgallery-next.png);
|
background-image:url(/img/hpgallery-next.png);
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
.section.instructor-quotes {
|
||||||
|
background: #971D31;
|
||||||
|
|
||||||
|
h2, h3, p {
|
||||||
|
color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 1em;
|
||||||
|
font-style: italic;
|
||||||
|
margin-right: 30px;
|
||||||
|
margin-top: 20px;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.image {
|
||||||
|
float: left;
|
||||||
|
margin-right: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.section.features {
|
.section.features {
|
||||||
background: #4F7E89;
|
background: #4F7E89;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -1212,6 +1426,12 @@ a.tag:hover {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.section.support {
|
||||||
|
padding-bottom: 100px;
|
||||||
|
p {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
.section.features .col {
|
.section.features .col {
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
|
|
||||||
|
@ -1334,9 +1554,9 @@ a.tag:hover {
|
||||||
font-family: $times;
|
font-family: $times;
|
||||||
color: #afcbd1;
|
color: #afcbd1;
|
||||||
}
|
}
|
||||||
a {
|
|
||||||
color: #afcbd1;
|
|
||||||
}
|
}
|
||||||
|
.section.press .quote a {
|
||||||
|
color: #afcbd1;
|
||||||
}
|
}
|
||||||
.section.plans {
|
.section.plans {
|
||||||
@media(max-device-width:480px), screen and (max-width:550px) {
|
@media(max-device-width:480px), screen and (max-width:550px) {
|
||||||
|
@ -1503,7 +1723,7 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.section.plans.welcome {
|
.section.plans.welcome {
|
||||||
padding: 63px 3%;
|
padding: 63px 3% 0 3%;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #5e95a1;
|
color: #5e95a1;
|
||||||
|
@ -1713,8 +1933,20 @@ a.tag:hover {
|
||||||
.browse-page h1 {
|
.browse-page h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.browse-page .tags {
|
.browse-page .row.content.misc {
|
||||||
padding: 0 30px 40px 30px;
|
form {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 68px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.misc-page .pagination {
|
.misc-page .pagination {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -1764,6 +1996,12 @@ a.tag:hover {
|
||||||
.filename {
|
.filename {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
width: 70%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.row.content {
|
.row.content {
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
|
@ -1864,3 +2102,78 @@ a.tag:hover {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
table#latest-visitors {
|
||||||
|
width: 100%;
|
||||||
|
color: #777;
|
||||||
|
font-size: .8em;
|
||||||
|
|
||||||
|
td {
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 0;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.location {
|
||||||
|
color: #2f4149;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.paths {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#earth_div {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
.content.misc-page.columns .col.globe {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
.news-feed .content.misc-page .col-50 {
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.intro-List.kickstarter .col {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: .8em;
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
&:first-child{
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 1%;
|
||||||
|
}
|
||||||
|
.title a {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.welcome.kickstarter {
|
||||||
|
background: #daeea5 url(/img/tutorialthumbnail.png) no-repeat;
|
||||||
|
background-position: right center;
|
||||||
|
background-size: auto 100%;
|
||||||
|
padding: 15px 100px 4px 23px;
|
||||||
|
margin-bottom: 13px;
|
||||||
|
font-size: 95%;
|
||||||
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
|
background-size: 32%;
|
||||||
|
background-position: right top;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: .2em;
|
||||||
|
a {
|
||||||
|
color: #2c3e50!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
float:left;
|
float:left;
|
||||||
margin-bottom:$spacing*2;
|
margin-bottom:$spacing*2;
|
||||||
color: #666;
|
color: #666;
|
||||||
@include box-shadow(1px 1px 2px 0px rgba(0, 0, 0, 0.18));
|
@include box-shadow(0px 1px 3px 0px rgba(0, 0, 0, 0.18));
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px){
|
@media (max-device-width:480px), screen and (max-width:800px){
|
||||||
width:50%
|
width:50%
|
||||||
|
|
|
@ -17,10 +17,22 @@ describe 'dashboard' do
|
||||||
visit '/dashboard'
|
visit '/dashboard'
|
||||||
click_link 'New Folder'
|
click_link 'New Folder'
|
||||||
fill_in 'name', with: 'testimages'
|
fill_in 'name', with: 'testimages'
|
||||||
click_button 'Create'
|
#click_button 'Create'
|
||||||
|
all('#createDir button[type=submit]').first.click
|
||||||
page.must_have_content /testimages/
|
page.must_have_content /testimages/
|
||||||
File.directory?(@site.files_path('testimages')).must_equal true
|
File.directory?(@site.files_path('testimages')).must_equal true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates a new file' do
|
||||||
|
random = SecureRandom.uuid.gsub('-', '')
|
||||||
|
visit '/dashboard'
|
||||||
|
click_link 'New File'
|
||||||
|
fill_in 'filename', with: "#{random}.html"
|
||||||
|
#click_button 'Create'
|
||||||
|
all('#createFile button[type=submit]').first.click
|
||||||
|
page.must_have_content /#{random}\.html/
|
||||||
|
File.exist?(@site.files_path("#{random}.html")).must_equal true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
61
tests/acceptance/education_tests.rb
Normal file
61
tests/acceptance/education_tests.rb
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
require_relative './environment.rb'
|
||||||
|
|
||||||
|
Capybara.register_driver :poltergeist do |app|
|
||||||
|
Capybara::Poltergeist::Driver.new(app, js_errors: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'signup' do
|
||||||
|
include Capybara::DSL
|
||||||
|
|
||||||
|
def fill_in_valid
|
||||||
|
@site = Fabricate.attributes_for(:site)
|
||||||
|
@class_tag = SecureRandom.uuid.gsub('-', '')[0..Tag::NAME_LENGTH_MAX-1]
|
||||||
|
fill_in 'username', with: @site[:username]
|
||||||
|
fill_in 'password', with: @site[:password]
|
||||||
|
fill_in 'email', with: @site[:email]
|
||||||
|
fill_in 'new_tags_string', with: @class_tag
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Capybara.default_driver = :poltergeist
|
||||||
|
Capybara.reset_sessions!
|
||||||
|
visit '/education'
|
||||||
|
page.must_have_content 'Neocities' # Used to force load wait
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
Capybara.default_driver = :rack_test
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds with valid data' do
|
||||||
|
fill_in_valid
|
||||||
|
click_button 'Create My Site'
|
||||||
|
page.must_have_content /Welcome to your new site/
|
||||||
|
|
||||||
|
index_file_path = File.join Site::SITE_FILES_ROOT, @site[:username], 'index.html'
|
||||||
|
File.exist?(index_file_path).must_equal true
|
||||||
|
|
||||||
|
site = Site[username: @site[:username]]
|
||||||
|
site.site_files.length.must_equal 4
|
||||||
|
site.site_changed.must_equal false
|
||||||
|
site.site_updated_at.must_equal nil
|
||||||
|
site.is_education.must_equal true
|
||||||
|
site.tags.length.must_equal 1
|
||||||
|
site.tags.first.name.must_equal @class_tag
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails to create for existing site' do
|
||||||
|
@existing_site = Fabricate :site
|
||||||
|
fill_in_valid
|
||||||
|
fill_in :username, with: @existing_site.username
|
||||||
|
click_button 'Create My Site'
|
||||||
|
page.must_have_content 'already taken'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails for multiple tags' do
|
||||||
|
fill_in_valid
|
||||||
|
fill_in :new_tags_string, with: 'derp, ie'
|
||||||
|
click_button 'Create My Site'
|
||||||
|
page.must_have_content 'Must only have one tag'
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,3 +5,5 @@ Capybara.app = Sinatra::Application
|
||||||
def teardown
|
def teardown
|
||||||
Capybara.reset_sessions!
|
Capybara.reset_sessions!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Capybara.default_wait_time = 5
|
||||||
|
|
|
@ -11,8 +11,8 @@ describe '/' do
|
||||||
|
|
||||||
it 'loads the news feed with welcome' do
|
it 'loads the news feed with welcome' do
|
||||||
visit '/'
|
visit '/'
|
||||||
page.body.must_match /Neocities news feed/i
|
page.body.must_match /Thanks for joining the Neocities community/i
|
||||||
page.body.must_match /You aren’t following any websites yet/i
|
page.body.wont_match /You aren’t following any websites yet/i
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays a follow and an unrelated follow' do
|
it 'displays a follow and an unrelated follow' do
|
||||||
|
|
|
@ -30,16 +30,19 @@ describe 'signup' do
|
||||||
Capybara.default_driver = :poltergeist
|
Capybara.default_driver = :poltergeist
|
||||||
Capybara.reset_sessions!
|
Capybara.reset_sessions!
|
||||||
visit_signup
|
visit_signup
|
||||||
|
page.must_have_content 'Neocities' # Used to force load wait
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
Capybara.default_driver = :rack_test
|
Capybara.default_driver = :rack_test
|
||||||
|
BlockedIp.where(ip: '127.0.0.1').delete
|
||||||
|
DB[:sites].where(is_banned: true).delete
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'succeeds with valid data' do
|
it 'succeeds with valid data' do
|
||||||
fill_in_valid
|
fill_in_valid
|
||||||
click_signup_button
|
click_signup_button
|
||||||
site_created?.must_equal true
|
site_created?
|
||||||
|
|
||||||
index_file_path = File.join Site::SITE_FILES_ROOT, @site[:username], 'index.html'
|
index_file_path = File.join Site::SITE_FILES_ROOT, @site[:username], 'index.html'
|
||||||
File.exist?(index_file_path).must_equal true
|
File.exist?(index_file_path).must_equal true
|
||||||
|
@ -48,19 +51,36 @@ describe 'signup' do
|
||||||
site.site_files.length.must_equal 4
|
site.site_files.length.must_equal 4
|
||||||
site.site_changed.must_equal false
|
site.site_changed.must_equal false
|
||||||
site.site_updated_at.must_equal nil
|
site.site_updated_at.must_equal nil
|
||||||
|
site.is_education.must_equal false
|
||||||
|
|
||||||
site.ip.must_equal Site.hash_ip('127.0.0.1')
|
site.ip.must_equal Site.hash_ip('127.0.0.1')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails to create for existing site' do
|
it 'fails if site with same ip has been banned' do
|
||||||
|
@banned_site = Fabricate :site
|
||||||
|
@banned_site.is_banned = true
|
||||||
|
@banned_site.save_changes
|
||||||
|
|
||||||
fill_in_valid
|
fill_in_valid
|
||||||
click_signup_button
|
click_signup_button
|
||||||
page.must_have_content 'Welcome to Neocities'
|
Site[username: @site[:username]].must_be_nil
|
||||||
Capybara.reset_sessions!
|
current_path.must_equal '/'
|
||||||
visit_signup
|
page.wont_have_content 'Welcome to Neocities'
|
||||||
sleep 0.3
|
end
|
||||||
fill_in 'username', with: @site[:username]
|
|
||||||
fill_in 'password', with: @site[:password]
|
it 'fails if IP is banned from blocked ips list' do
|
||||||
|
DB[:blocked_ips].insert(ip: '127.0.0.1', created_at: Time.now)
|
||||||
|
fill_in_valid
|
||||||
|
click_signup_button
|
||||||
|
Site[username: @site[:username]].must_be_nil
|
||||||
|
current_path.must_equal '/'
|
||||||
|
page.wont_have_content 'Welcome to Neocities'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails to create for existing site' do
|
||||||
|
@existing_site = Fabricate :site
|
||||||
|
fill_in_valid
|
||||||
|
fill_in 'username', with: @existing_site.username
|
||||||
click_signup_button
|
click_signup_button
|
||||||
page.must_have_content 'already taken'
|
page.must_have_content 'already taken'
|
||||||
end
|
end
|
||||||
|
@ -113,9 +133,6 @@ describe 'signup' do
|
||||||
page.must_have_content /email.+exists/
|
page.must_have_content /email.+exists/
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "$$$$$$$$$$$$$$$$$$$$$$ TODO FIX TAGS TESTS"
|
|
||||||
|
|
||||||
=begin
|
|
||||||
it 'succeeds with no tags' do
|
it 'succeeds with no tags' do
|
||||||
fill_in_valid
|
fill_in_valid
|
||||||
fill_in 'new_tags_string', with: ''
|
fill_in 'new_tags_string', with: ''
|
||||||
|
@ -179,9 +196,10 @@ puts "$$$$$$$$$$$$$$$$$$$$$$ TODO FIX TAGS TESTS"
|
||||||
fill_in 'new_tags_string', with: 'one, one'
|
fill_in 'new_tags_string', with: 'one, one'
|
||||||
click_signup_button
|
click_signup_button
|
||||||
|
|
||||||
site = Site.last
|
page.must_have_content /Welcome to Neocities/
|
||||||
|
|
||||||
|
site = Site[username: @site[:username]]
|
||||||
site.tags.length.must_equal 1
|
site.tags.length.must_equal 1
|
||||||
site.tags.first.name.must_equal 'one'
|
site.tags.first.name.must_equal 'one'
|
||||||
end
|
end
|
||||||
=end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe 'site page' do
|
||||||
click_button 'Post'
|
click_button 'Post'
|
||||||
@site.profile_comments.count.must_equal 1
|
@site.profile_comments.count.must_equal 1
|
||||||
profile_comment = @site.profile_comments.first
|
profile_comment = @site.profile_comments.first
|
||||||
profile_comment.actioning_site.must_equal @commenting_site
|
profile_comment.actioning_site.id.must_equal @commenting_site.id
|
||||||
profile_comment.message.must_equal 'I love your site!'
|
profile_comment.message.must_equal 'I love your site!'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
56
tests/admin_tests.rb
Normal file
56
tests/admin_tests.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
require_relative './environment.rb'
|
||||||
|
require 'rack/test'
|
||||||
|
|
||||||
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
def app
|
||||||
|
Sinatra::Application
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'email blasting' do
|
||||||
|
before do
|
||||||
|
EmailWorker.jobs.clear
|
||||||
|
@admin_site = Fabricate :site, is_admin: true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works' do
|
||||||
|
DB['update sites set changed_count=?', 0].first
|
||||||
|
relevant_emails = []
|
||||||
|
|
||||||
|
sites_emailed_count = Site::EMAIL_BLAST_MAXIMUM_PER_DAY*2
|
||||||
|
|
||||||
|
sites_emailed_count.times {
|
||||||
|
site = Fabricate :site, updated_at: Time.now, changed_count: 1
|
||||||
|
relevant_emails << site.email
|
||||||
|
}
|
||||||
|
|
||||||
|
EmailWorker.jobs.clear
|
||||||
|
|
||||||
|
time = Time.now
|
||||||
|
|
||||||
|
Timecop.freeze(time) do
|
||||||
|
post '/admin/email', {
|
||||||
|
:csrf_token => 'abcd',
|
||||||
|
:subject => 'Subject Test',
|
||||||
|
:body => 'Body Test'}, {
|
||||||
|
'rack.session' => { 'id' => @admin_site.id, '_csrf_token' => 'abcd' }
|
||||||
|
}
|
||||||
|
|
||||||
|
relevant_jobs = EmailWorker.jobs.select{|j| relevant_emails.include?(j['args'].first['to']) }
|
||||||
|
relevant_jobs.length.must_equal sites_emailed_count
|
||||||
|
|
||||||
|
relevant_jobs.each do |job|
|
||||||
|
args = job['args'].first
|
||||||
|
args['from'].must_equal 'Kyle from Neocities <kyle@neocities.org>'
|
||||||
|
args['subject'].must_equal 'Subject Test'
|
||||||
|
args['body'].must_equal 'Body Test'
|
||||||
|
end
|
||||||
|
|
||||||
|
relevant_jobs.select {|j| j['at'].nil? || j['at'] == Time.now.to_f}.length.must_equal 1
|
||||||
|
relevant_jobs.select {|j| j['at'] == (Time.now + 0.5).to_f}.length.must_equal 1
|
||||||
|
|
||||||
|
relevant_jobs.select {|j| j['at'] == (time+1.day.to_i).to_f}.length.must_equal 1
|
||||||
|
relevant_jobs.select {|j| j['at'] == (time+1.day.to_i+0.5).to_f}.length.must_equal 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -89,7 +89,7 @@ describe 'api delete' do
|
||||||
it 'succeeds with weird filenames' do
|
it 'succeeds with weird filenames' do
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
@site.store_file 't$st.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
@site.store_files [{filename: 't$st.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||||
post '/api/delete', filenames: ['t$st.jpg']
|
post '/api/delete', filenames: ['t$st.jpg']
|
||||||
res[:result].must_equal 'success'
|
res[:result].must_equal 'success'
|
||||||
|
|
||||||
|
@ -102,16 +102,37 @@ describe 'api delete' do
|
||||||
it 'fails with missing files' do
|
it 'fails with missing files' do
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
@site.store_file 'test.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
@site.store_files [{filename: 'test.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||||
post '/api/delete', filenames: ['doesntexist.jpg']
|
post '/api/delete', filenames: ['doesntexist.jpg']
|
||||||
res[:error_type].must_equal 'missing_files'
|
res[:error_type].must_equal 'missing_files'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'fails to delete site directory' do
|
||||||
|
create_site
|
||||||
|
basic_authorize @user, @pass
|
||||||
|
post '/api/delete', filenames: ['/']
|
||||||
|
res[:error_type].must_equal 'cannot_delete_site_directory'
|
||||||
|
File.exist?(@site.files_path).must_equal true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails to delete other directories' do
|
||||||
|
create_site
|
||||||
|
@other_site = @site
|
||||||
|
create_site
|
||||||
|
basic_authorize @user, @pass
|
||||||
|
post '/api/delete', filenames: ["../#{@other_site.username}"]
|
||||||
|
File.exist?(@other_site.base_files_path).must_equal true
|
||||||
|
res[:error_type].must_equal 'missing_files'
|
||||||
|
post '/api/delete', filenames: ["../#{@other_site.username}/index.html"]
|
||||||
|
File.exist?(@other_site.base_files_path+'/index.html').must_equal true
|
||||||
|
res[:error_type].must_equal 'missing_files'
|
||||||
|
end
|
||||||
|
|
||||||
it 'succeeds with valid filenames' do
|
it 'succeeds with valid filenames' do
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
@site.store_file 'test.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
@site.store_files [{filename: 'test.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||||
@site.store_file 'test2.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
@site.store_files [{filename: 'test2.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||||
post '/api/delete', filenames: ['test.jpg', 'test2.jpg']
|
post '/api/delete', filenames: ['test.jpg', 'test2.jpg']
|
||||||
res[:result].must_equal 'success'
|
res[:result].must_equal 'success'
|
||||||
site_file_exists?('test.jpg').must_equal false
|
site_file_exists?('test.jpg').must_equal false
|
||||||
|
@ -139,6 +160,19 @@ describe 'api upload' do
|
||||||
res[:error_type].must_equal 'missing_files'
|
res[:error_type].must_equal 'missing_files'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'fails with too many files' do
|
||||||
|
create_site
|
||||||
|
basic_authorize @user, @pass
|
||||||
|
@site.plan_feature(:maximum_site_files).times {
|
||||||
|
uuid = SecureRandom.uuid.gsub('-', '')+'.html'
|
||||||
|
@site.add_site_file path: uuid
|
||||||
|
}
|
||||||
|
post '/api/upload', {
|
||||||
|
'/lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
}
|
||||||
|
res[:error_type].must_equal 'too_many_files'
|
||||||
|
end
|
||||||
|
|
||||||
it 'resists directory traversal attack' do
|
it 'resists directory traversal attack' do
|
||||||
create_site
|
create_site
|
||||||
basic_authorize @user, @pass
|
basic_authorize @user, @pass
|
||||||
|
@ -167,14 +201,6 @@ describe 'api upload' do
|
||||||
'/' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
'/' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
}
|
}
|
||||||
res[:error_type].must_equal 'invalid_file_type'
|
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
|
end
|
||||||
|
|
||||||
it 'fails for file with no extension' do
|
it 'fails for file with no extension' do
|
||||||
|
|
|
@ -18,21 +18,25 @@ describe 'site_files' do
|
||||||
before do
|
before do
|
||||||
@site = Fabricate :site
|
@site = Fabricate :site
|
||||||
ThumbnailWorker.jobs.clear
|
ThumbnailWorker.jobs.clear
|
||||||
|
PurgeCacheOrderWorker.jobs.clear
|
||||||
PurgeCacheWorker.jobs.clear
|
PurgeCacheWorker.jobs.clear
|
||||||
ScreenshotWorker.jobs.clear
|
ScreenshotWorker.jobs.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'delete' do
|
describe 'delete' do
|
||||||
it 'works' do
|
it 'works' do
|
||||||
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
upload 'files[]' => uploaded_file
|
||||||
|
@site.reload.space_used.must_equal uploaded_file.size
|
||||||
file_path = @site.files_path 'test.jpg'
|
file_path = @site.files_path 'test.jpg'
|
||||||
File.exists?(file_path).must_equal true
|
File.exists?(file_path).must_equal true
|
||||||
delete_file filename: 'test.jpg'
|
delete_file filename: 'test.jpg'
|
||||||
File.exists?(file_path).must_equal false
|
File.exists?(file_path).must_equal false
|
||||||
SiteFile[site_id: @site.id, path: 'test.jpg'].must_be_nil
|
SiteFile[site_id: @site.id, path: 'test.jpg'].must_be_nil
|
||||||
|
@site.reload.space_used.must_equal 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'deletes all files in a directory' do
|
it 'deletes a directory and all files in it' do
|
||||||
upload(
|
upload(
|
||||||
'dir' => 'test',
|
'dir' => 'test',
|
||||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
@ -45,6 +49,21 @@ describe 'site_files' do
|
||||||
@site.site_files.select {|f| f.path =~ /^test\//}.length.must_equal 0
|
@site.site_files.select {|f| f.path =~ /^test\//}.length.must_equal 0
|
||||||
@site.site_files.select {|f| f.path =~ /^test/}.length.must_equal 1
|
@site.site_files.select {|f| f.path =~ /^test/}.length.must_equal 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'goes back to deleting directory' do
|
||||||
|
upload(
|
||||||
|
'dir' => 'test',
|
||||||
|
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
)
|
||||||
|
delete_file filename: 'test/test.jpg'
|
||||||
|
last_response.headers['Location'].must_equal "http://example.org/dashboard?dir=test"
|
||||||
|
|
||||||
|
upload(
|
||||||
|
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
)
|
||||||
|
delete_file filename: 'test.jpg'
|
||||||
|
last_response.headers['Location'].must_equal "http://example.org/dashboard"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'upload' do
|
describe 'upload' do
|
||||||
|
@ -82,10 +101,31 @@ describe 'site_files' do
|
||||||
args = ScreenshotWorker.jobs.first['args']
|
args = ScreenshotWorker.jobs.first['args']
|
||||||
args.first.must_equal @site.username
|
args.first.must_equal @site.username
|
||||||
args.last.must_equal 'index.html'
|
args.last.must_equal 'index.html'
|
||||||
@site.title.must_equal "#{@site.username}.neocities.org"
|
@site.title.must_equal "The web site of #{@site.username}"
|
||||||
@site.reload
|
@site.reload
|
||||||
@site.site_changed.must_equal true
|
@site.site_changed.must_equal true
|
||||||
@site.title.must_equal 'Hello?'
|
@site.title.must_equal 'Hello?'
|
||||||
|
|
||||||
|
# Purge cache needs to flush / and index.html for either scenario.
|
||||||
|
PurgeCacheOrderWorker.jobs.length.must_equal 2
|
||||||
|
first_purge = PurgeCacheOrderWorker.jobs.first
|
||||||
|
dirname_purge = PurgeCacheOrderWorker.jobs.last
|
||||||
|
|
||||||
|
username, pathname = first_purge['args']
|
||||||
|
username.must_equal @site.username
|
||||||
|
pathname.must_equal '/index.html'
|
||||||
|
username, pathame = nil
|
||||||
|
username, pathname = dirname_purge['args']
|
||||||
|
username.must_equal @site.username
|
||||||
|
pathname.must_equal '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'provides the correct space used after overwriting an existing file' do
|
||||||
|
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
upload 'files[]' => uploaded_file
|
||||||
|
second_uploaded_file = Rack::Test::UploadedFile.new('./tests/files/img/test.jpg', 'image/jpeg')
|
||||||
|
upload 'files[]' => second_uploaded_file
|
||||||
|
@site.reload.space_used.must_equal second_uploaded_file.size
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not change title for subdir index.html' do
|
it 'does not change title for subdir index.html' do
|
||||||
|
@ -97,15 +137,19 @@ describe 'site_files' do
|
||||||
@site.reload.title.must_equal title
|
@site.reload.title.must_equal title
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
it 'succeeds with valid file' do
|
it 'succeeds with valid file' do
|
||||||
upload 'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||||
|
upload 'files[]' => uploaded_file
|
||||||
last_response.body.must_match /successfully uploaded/i
|
last_response.body.must_match /successfully uploaded/i
|
||||||
File.exists?(@site.files_path('test.jpg')).must_equal true
|
File.exists?(@site.files_path('test.jpg')).must_equal true
|
||||||
|
|
||||||
queue_args = PurgeCacheWorker.jobs.first['args'].first
|
username, path = PurgeCacheOrderWorker.jobs.first['args']
|
||||||
queue_args['site'].must_equal @site.username
|
username.must_equal @site.username
|
||||||
queue_args['path'].must_equal '/test.jpg'
|
path.must_equal '/test.jpg'
|
||||||
|
|
||||||
|
@site.reload
|
||||||
|
@site.space_used.wont_equal 0
|
||||||
|
@site.space_used.must_equal uploaded_file.size
|
||||||
|
|
||||||
ThumbnailWorker.jobs.length.must_equal 1
|
ThumbnailWorker.jobs.length.must_equal 1
|
||||||
ThumbnailWorker.drain
|
ThumbnailWorker.drain
|
||||||
|
@ -114,7 +158,7 @@ describe 'site_files' do
|
||||||
File.exists?(@site.thumbnail_path('test.jpg', resolution)).must_equal true
|
File.exists?(@site.thumbnail_path('test.jpg', resolution)).must_equal true
|
||||||
end
|
end
|
||||||
|
|
||||||
@site.site_changed.must_equal false
|
@site.site_changed.must_equal true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails with unsupported file' do
|
it 'fails with unsupported file' do
|
||||||
|
@ -153,9 +197,18 @@ describe 'site_files' do
|
||||||
last_response.body.must_match /successfully uploaded/i
|
last_response.body.must_match /successfully uploaded/i
|
||||||
File.exists?(@site.files_path('derpie/derptest/test.jpg')).must_equal true
|
File.exists?(@site.files_path('derpie/derptest/test.jpg')).must_equal true
|
||||||
|
|
||||||
PurgeCacheWorker.jobs.length.must_equal 1
|
PurgeCacheOrderWorker.jobs.length.must_equal 1
|
||||||
queue_args = PurgeCacheWorker.jobs.first['args'].first
|
username, path = PurgeCacheOrderWorker.jobs.first['args']
|
||||||
queue_args['path'].must_equal '/derpie/derptest/test.jpg'
|
path.must_equal '/derpie/derptest/test.jpg'
|
||||||
|
|
||||||
|
PurgeCacheOrderWorker.drain
|
||||||
|
|
||||||
|
PurgeCacheWorker.jobs.length.must_equal 2
|
||||||
|
ip, username, path = PurgeCacheWorker.jobs.first['args']
|
||||||
|
ip.must_equal '10.0.0.1'
|
||||||
|
username.must_equal @site.username
|
||||||
|
path.must_equal '/derpie/derptest/test.jpg'
|
||||||
|
PurgeCacheWorker.jobs.last['args'].first.must_equal '10.0.0.2'
|
||||||
|
|
||||||
ThumbnailWorker.jobs.length.must_equal 1
|
ThumbnailWorker.jobs.length.must_equal 1
|
||||||
ThumbnailWorker.drain
|
ThumbnailWorker.drain
|
||||||
|
|
|
@ -53,10 +53,12 @@ describe Site do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should match plan_type' do
|
it 'should match plan_type' do
|
||||||
%w{supporter neko catbus fatcat}.each do |plan_type|
|
%w{supporter free}.each do |plan_type|
|
||||||
site = Fabricate :site, plan_type: plan_type
|
site = Fabricate :site, plan_type: plan_type
|
||||||
site.plan_type.must_equal plan_type
|
site.plan_type.must_equal plan_type
|
||||||
end
|
end
|
||||||
|
site = Fabricate :site, plan_type: nil
|
||||||
|
site.plan_type.must_equal 'free'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
153
tests/stat_tests.rb
Normal file
153
tests/stat_tests.rb
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
require_relative './environment.rb'
|
||||||
|
|
||||||
|
STAT_LOGS_PATH = 'tests/stat_logs'
|
||||||
|
STAT_LOGS_DIR_MATCH = "#{STAT_LOGS_PATH}/*.log"
|
||||||
|
|
||||||
|
describe 'stats' do
|
||||||
|
before do
|
||||||
|
Dir[STAT_LOGS_DIR_MATCH].each {|f| FileUtils.rm f}
|
||||||
|
@site_one = Fabricate :site
|
||||||
|
@site_two = Fabricate :site
|
||||||
|
|
||||||
|
@time = Time.now
|
||||||
|
@time_iso8601 = @time.iso8601
|
||||||
|
|
||||||
|
log = [
|
||||||
|
"#{@time_iso8601}\t#{@site_one.username}\t5000\t/\t67.180.75.140\thttp://example.com",
|
||||||
|
"#{@time_iso8601}\t#{@site_one.username}\t5000\t/\t67.180.75.140\thttp://example.com",
|
||||||
|
"#{@time_iso8601}\t#{@site_one.username}\t5000\t/\t172.56.16.152\thttp://example.com",
|
||||||
|
"#{@time_iso8601}\t#{@site_one.username}\t5000\t/\t172.56.16.152\t-",
|
||||||
|
"#{@time_iso8601}\t#{@site_two.username}\t5000\t/\t67.180.75.140\thttp://example.com",
|
||||||
|
"#{@time_iso8601}\t#{@site_two.username}\t5000\t/\t127.0.0.1\t-",
|
||||||
|
"#{@time_iso8601}\t#{@site_two.username}\t5000\t/derp.html\t127.0.0.2\thttps://example.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
File.open("tests/stat_logs/#{SecureRandom.uuid}.log", 'w') do |file|
|
||||||
|
file.write log.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deals with spaces in paths' do
|
||||||
|
@site = Fabricate :site
|
||||||
|
File.open("tests/stat_logs/#{SecureRandom.uuid}.log", 'w') do |file|
|
||||||
|
file.write "2015-05-02T21:16:35+00:00\t#{@site.username}\t612917\t/images/derpie space.png\t67.180.75.140\thttp://derp.com\n"
|
||||||
|
file.write "2015-05-02T21:16:35+00:00\t#{@site.username}\t612917\t/images/derpie space.png\t67.180.75.140\thttp://derp.com\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
Stat.parse_logfiles STAT_LOGS_PATH
|
||||||
|
@site.stats.first.bandwidth.must_equal 612917*2
|
||||||
|
#@site.stat_referrers.first.url.must_equal 'http://derp.com'
|
||||||
|
#@site.stat_locations.first.city_name.must_equal 'Menlo Park'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deals with spaces in referrer' do
|
||||||
|
@site = Fabricate :site
|
||||||
|
File.open("tests/stat_logs/#{SecureRandom.uuid}.log", 'w') do |file|
|
||||||
|
file.write "2015-05-02T21:16:35+00:00\t#{@site.username}\t612917\t/images/derpie space.png\t67.180.75.140\thttp://derp.com?q=what the lump\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
Stat.parse_logfiles STAT_LOGS_PATH
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prunes logs for free sites' do
|
||||||
|
@free_site = Fabricate :site
|
||||||
|
@supporter_site = Fabricate :site, plan_type: 'supporter'
|
||||||
|
|
||||||
|
day = Date.today
|
||||||
|
(Stat::FREE_RETAINMENT_DAYS+1).times do |i|
|
||||||
|
[@free_site, @supporter_site].each do |site|
|
||||||
|
Stat.create site_id: site.id, created_at: day
|
||||||
|
end
|
||||||
|
day = day - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
count_site_ids = [@free_site.id, @supporter_site.id]
|
||||||
|
expected_stat_count = (Stat::FREE_RETAINMENT_DAYS+1)*2
|
||||||
|
|
||||||
|
Stat.where(site_id: count_site_ids).count.must_equal expected_stat_count
|
||||||
|
Stat.prune!
|
||||||
|
Stat.where(site_id: count_site_ids).count.must_equal expected_stat_count-1
|
||||||
|
Stat.where(site_id: @supporter_site.id).count.must_equal expected_stat_count/2
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prunes referrers' do
|
||||||
|
stat_referrer_now = @site_one.add_stat_referrer created_at: Date.today, url: 'http://example.com/now'
|
||||||
|
stat_referrer = @site_one.add_stat_referrer created_at: (StatReferrer::RETAINMENT_DAYS-1).days.ago, url: 'http://example.com'
|
||||||
|
StatReferrer[stat_referrer.id].wont_be_nil
|
||||||
|
@site_one.stat_referrers_dataset.count.must_equal 2
|
||||||
|
StatReferrer.prune!
|
||||||
|
@site_one.stat_referrers_dataset.count.must_equal 1
|
||||||
|
StatReferrer[stat_referrer.id].must_be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prunes locations' do
|
||||||
|
stat_location = @site_one.add_stat_location(
|
||||||
|
created_at: (StatLocation::RETAINMENT_DAYS-1).days.ago,
|
||||||
|
country_code2: 'US',
|
||||||
|
region_name: 'Minnesota',
|
||||||
|
city_name: 'Minneapolis'
|
||||||
|
)
|
||||||
|
StatLocation[stat_location.id].wont_be_nil
|
||||||
|
StatLocation.prune!
|
||||||
|
StatLocation[stat_location.id].must_be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prunes paths' do
|
||||||
|
stat_path = @site_one.add_stat_path(
|
||||||
|
created_at: (StatPath::RETAINMENT_DAYS-1).days.ago,
|
||||||
|
name: '/derpie.html'
|
||||||
|
)
|
||||||
|
StatPath[stat_path.id].wont_be_nil
|
||||||
|
StatPath.prune!
|
||||||
|
StatPath[stat_path.id].must_be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses logfile' do
|
||||||
|
Stat.parse_logfiles STAT_LOGS_PATH
|
||||||
|
|
||||||
|
@site_one.reload
|
||||||
|
@site_one.hits.must_equal 4
|
||||||
|
@site_one.views.must_equal 2
|
||||||
|
stat = @site_one.stats.first
|
||||||
|
stat.hits.must_equal 4
|
||||||
|
stat.views.must_equal 2
|
||||||
|
stat.bandwidth.must_equal 20_000
|
||||||
|
|
||||||
|
#@site_one.stat_referrers.count.must_equal 1
|
||||||
|
#stat_referrer = @site_one.stat_referrers.first
|
||||||
|
#stat_referrer.url.must_equal 'http://example.com'
|
||||||
|
#stat_referrer.created_at.must_equal @time.to_date
|
||||||
|
#stat_referrer.views.must_equal 2
|
||||||
|
|
||||||
|
#@site_one.stat_paths.length.must_equal 1
|
||||||
|
#stat_path = @site_one.stat_paths.first
|
||||||
|
#stat_path.name.must_equal '/'
|
||||||
|
#stat_path.views.must_equal 4
|
||||||
|
|
||||||
|
#@site_one.stat_locations.length.must_equal 2
|
||||||
|
#stat_location = @site_one.stat_locations.first
|
||||||
|
#stat_location.country_code2.must_equal 'US'
|
||||||
|
#stat_location.region_name.must_equal 'CA'
|
||||||
|
#stat_location.city_name.must_equal 'Menlo Park'
|
||||||
|
#stat_location.views.must_equal 1
|
||||||
|
|
||||||
|
@site_two.reload
|
||||||
|
@site_two.hits.must_equal 3
|
||||||
|
@site_two.views.must_equal 3
|
||||||
|
stat = @site_two.stats.first
|
||||||
|
stat.hits.must_equal 3
|
||||||
|
stat.views.must_equal 3
|
||||||
|
stat.bandwidth.must_equal 15_000
|
||||||
|
#@site_two.stat_referrers.count.must_equal 2
|
||||||
|
#stat_referrer = @site_two.stat_referrers.first
|
||||||
|
#stat_referrer.url.must_equal 'http://example.com'
|
||||||
|
#stat_referrer.views.must_equal 2
|
||||||
|
|
||||||
|
#stat_paths = @site_two.stat_paths
|
||||||
|
#stat_paths.length.must_equal 2
|
||||||
|
#stat_paths.first.name.must_equal '/'
|
||||||
|
#stat_paths.last.name.must_equal '/derp.html'
|
||||||
|
|
||||||
|
# [geoip.city('67.180.75.140'), geoip.city('172.56.16.152')]
|
||||||
|
end
|
||||||
|
end
|
28
tests/workers/archive_worker_tests.rb
Normal file
28
tests/workers/archive_worker_tests.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
require_relative '../environment.rb'
|
||||||
|
|
||||||
|
describe ArchiveWorker do
|
||||||
|
it 'stores an IPFS archive' do
|
||||||
|
return if ENV['TRAVIS']
|
||||||
|
site = Fabricate :site
|
||||||
|
ipfs_hash = site.add_to_ipfs
|
||||||
|
ArchiveWorker.new.perform site.id
|
||||||
|
site.archives.length.must_equal 1
|
||||||
|
archive_one = site.archives.first
|
||||||
|
archive_one.ipfs_hash.must_equal ipfs_hash
|
||||||
|
archive_one.updated_at.wont_be_nil
|
||||||
|
|
||||||
|
new_updated_at = Time.now - 500
|
||||||
|
archive_one.update updated_at: new_updated_at
|
||||||
|
|
||||||
|
ArchiveWorker.new.perform site.id
|
||||||
|
archive_one.reload.updated_at.wont_equal new_updated_at
|
||||||
|
|
||||||
|
site.store_files [{filename: 'test.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||||
|
ArchiveWorker.new.perform site.id
|
||||||
|
|
||||||
|
site.reload
|
||||||
|
site.archives.length.must_equal 2
|
||||||
|
archive_two = site.archives_dataset.exclude(ipfs_hash: archive_one.ipfs_hash).first
|
||||||
|
archive_two.ipfs_hash.wont_be_nil
|
||||||
|
end
|
||||||
|
end
|
21
tests/workers/purge_cache_order_worker_tests.rb
Normal file
21
tests/workers/purge_cache_order_worker_tests.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
require_relative '../environment.rb'
|
||||||
|
|
||||||
|
describe PurgeCacheWorker do
|
||||||
|
before do
|
||||||
|
PurgeCacheOrderWorker.jobs.clear
|
||||||
|
PurgeCacheWorker.jobs.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'queues up purges' do
|
||||||
|
PurgeCacheOrderWorker.new.perform('kyledrake', '/test.jpg')
|
||||||
|
|
||||||
|
job_one_args = PurgeCacheWorker.jobs.first['args']
|
||||||
|
job_two_args = PurgeCacheWorker.jobs.last['args']
|
||||||
|
job_one_args[0].must_equal '10.0.0.1'
|
||||||
|
job_one_args[1].must_equal 'kyledrake'
|
||||||
|
job_one_args[2].must_equal '/test.jpg'
|
||||||
|
job_two_args[0].must_equal '10.0.0.2'
|
||||||
|
job_two_args[1].must_equal 'kyledrake'
|
||||||
|
job_two_args[2].must_equal '/test.jpg'
|
||||||
|
end
|
||||||
|
end
|
64
tests/workers/purge_cache_worker_tests.rb
Normal file
64
tests/workers/purge_cache_worker_tests.rb
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
require_relative '../environment.rb'
|
||||||
|
|
||||||
|
describe PurgeCacheWorker do
|
||||||
|
before do
|
||||||
|
@test_ip = '10.0.0.1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'throws exception without 200 or 404 http status' do
|
||||||
|
stub_request(:get, "http://#{@test_ip}/:cache/purge/test.jpg").
|
||||||
|
with(headers: {'Host' => 'kyledrake.neocities.org'})
|
||||||
|
.to_return(status: 503)
|
||||||
|
|
||||||
|
worker = PurgeCacheWorker.new
|
||||||
|
|
||||||
|
proc {
|
||||||
|
worker.perform @test_ip, 'kyledrake', '/test.jpg'
|
||||||
|
}.must_raise RestClient::ServiceUnavailable
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles 404 without exception' do
|
||||||
|
stub_request(:get, "http://#{@test_ip}/:cache/purge/test.jpg").
|
||||||
|
with(headers: {'Host' => 'kyledrake.neocities.org'})
|
||||||
|
.to_return(status: 404)
|
||||||
|
|
||||||
|
worker = PurgeCacheWorker.new
|
||||||
|
worker.perform @test_ip, 'kyledrake', '/test.jpg'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a purge request' do
|
||||||
|
stub_request(:get, "http://#{@test_ip}/:cache/purge/test.jpg").
|
||||||
|
with(headers: {'Host' => 'kyledrake.neocities.org'})
|
||||||
|
.to_return(status: 200)
|
||||||
|
|
||||||
|
worker = PurgeCacheWorker.new
|
||||||
|
worker.perform @test_ip, 'kyledrake', '/test.jpg'
|
||||||
|
|
||||||
|
assert_requested :get, "http://#{@test_ip}/:cache/purge/test.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles spaces correctly' do
|
||||||
|
stub_request(:get, "http://#{@test_ip}/:cache/purge/te st.jpg").
|
||||||
|
with(headers: {'Host' => 'kyledrake.neocities.org'})
|
||||||
|
.to_return(status: 200)
|
||||||
|
|
||||||
|
url = Addressable::URI.encode_component(
|
||||||
|
"http://#{@test_ip}/:cache/purge/te st.jpg",
|
||||||
|
Addressable::URI::CharacterClasses::QUERY
|
||||||
|
)
|
||||||
|
|
||||||
|
worker = PurgeCacheWorker.new
|
||||||
|
worker.perform @test_ip, 'kyledrake', '/te st.jpg'
|
||||||
|
|
||||||
|
assert_requested :get, url
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works without forward slash' do
|
||||||
|
stub_request(:get, "http://#{@test_ip}/:cache/purge/test.jpg").
|
||||||
|
with(headers: {'Host' => 'kyledrake.neocities.org'})
|
||||||
|
.to_return(status: 200)
|
||||||
|
|
||||||
|
worker = PurgeCacheWorker.new
|
||||||
|
worker.perform @test_ip, 'kyledrake', 'test.jpg'
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,9 +10,11 @@
|
||||||
<ul class="tiny h-Nav">
|
<ul class="tiny h-Nav">
|
||||||
<li><a href="/about">About</a></li>
|
<li><a href="/about">About</a></li>
|
||||||
<li><a href="/donate">Donate</a></li>
|
<li><a href="/donate">Donate</a></li>
|
||||||
|
<% unless is_education? %>
|
||||||
<li><a href="/blog">Blog</a></li>
|
<li><a href="/blog">Blog</a></li>
|
||||||
<li><a href="/api">API</a></li>
|
<li><a href="/api">API</a></li>
|
||||||
<li><a href="/press">Press</a></li>
|
<li><a href="/press">Press</a></li>
|
||||||
|
<% end %>
|
||||||
<li><a href="/terms" rel="nofollow">Terms</a></li>
|
<li><a href="/terms" rel="nofollow">Terms</a></li>
|
||||||
<li><a href="/privacy" rel="nofollow">Privacy</a></li>
|
<li><a href="/privacy" rel="nofollow">Privacy</a></li>
|
||||||
<li><a href="/contact" rel="nofollow">Contact</a></li>
|
<li><a href="/contact" rel="nofollow">Contact</a></li>
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
<% unless is_education? %>
|
||||||
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
|
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/?activity=mine"><span class="float-Left">Activity</span>
|
<a href="/?activity=mine"><span class="float-Left">Activity</span>
|
||||||
|
@ -39,8 +40,9 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
|
<% end %>
|
||||||
<li><a href="/dashboard">Edit Site</a></li>
|
<li><a href="/dashboard">Edit Site</a></li>
|
||||||
<li><a href="//<%= current_site.host %>" target="_blank">View Site</a></li>
|
<li><a href="<%= current_site.uri %>" target="_blank">View Site</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li><a href="/settings">Settings</a></li>
|
<li><a href="/settings">Settings</a></li>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<% if request.path == '/' && !signed_in? %>
|
<% if (request.path == '/' && !signed_in?) || request.path == '/education' %>
|
||||||
<li>
|
<li>
|
||||||
<a href="/">Neocities</a>
|
<a href="/">Neocities</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -6,12 +6,16 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/browse">Websites</a>
|
<a href="/browse">Websites</a>
|
||||||
</li>
|
</li>
|
||||||
|
<% unless is_education? %>
|
||||||
<li>
|
<li>
|
||||||
<a href="/activity">Activity</a>
|
<a href="/activity">Activity</a>
|
||||||
</li>
|
</li>
|
||||||
|
<% end %>
|
||||||
<li>
|
<li>
|
||||||
<a href="/tutorials">Learn</a>
|
<a href="/tutorials">Learn</a>
|
||||||
</li>
|
</li>
|
||||||
|
<% unless is_education? %>
|
||||||
<li>
|
<li>
|
||||||
<a href="/plan">Support Us<i class="fa fa-heart"><i class="fa fa-heart"></i></i></a>
|
<a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">Support Our Kickstarter<i class="fa fa-heart"><i class="fa fa-heart"></i></i></a>
|
||||||
</li>
|
</li>
|
||||||
|
<% end %>
|
||||||
|
|
41
views/_index_signup_script.erb
Normal file
41
views/_index_signup_script.erb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script src="/js/app.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$('#createSiteForm').on('submit', function(obj) {
|
||||||
|
$.post('/create_validate_all', $(obj.target).serialize(), function(errors) {
|
||||||
|
if(errors.length == 0) {
|
||||||
|
$.post('/create', $('#createSiteForm').serialize(), function(res) {
|
||||||
|
if($('input[name=is_education]').val() == 'true') {
|
||||||
|
window.location.href = '/dashboard'
|
||||||
|
} else {
|
||||||
|
window.location.href = '/welcome'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for(var i=0; i<errors.length;i++) {
|
||||||
|
if(errors[i][0] == 'captcha') {
|
||||||
|
var captchaDiv = $('#captcha-input')
|
||||||
|
captchaDiv.attr('data-original-title', errors[i][1])
|
||||||
|
captchaDiv.tooltip('show')
|
||||||
|
} else {
|
||||||
|
var ele = $('input[name='+errors[i][0]+']')
|
||||||
|
ele.attr('data-original-title', errors[i][1])
|
||||||
|
ele.tooltip('show')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$('input[type=text],input[type=password]').on('change focusout', function(obj) {
|
||||||
|
$.post('/create_validate', {field: obj.target.name, value: obj.target.value, is_education: $('input[name=is_education]')[0].value, csrf_token: '<%= csrf_token %>'}, function(res) {
|
||||||
|
if(res.result == 'ok') {
|
||||||
|
return $(obj.target).tooltip('hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
$(obj.target).attr('data-original-title', res.error)
|
||||||
|
$(obj.target).tooltip('show')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<div id="comment-template" style="display: none">
|
<div id="comment-template" style="display: none">
|
||||||
<form onsubmit="Comment.create({{ eventId }}, '<%= csrf_token %>', this); location.reload(); return false">
|
<form onsubmit="Comment.create({{ eventId }}, '<%= csrf_token %>', this); return false">
|
||||||
<input name="comment" type="text" autocomplete="off" maxlength="<%= Site::MAX_COMMENT_SIZE %>" style="width: 100%" placeholder="Comment on this...">
|
<input name="comment" type="text" autocomplete="off" maxlength="<%= Site::MAX_COMMENT_SIZE %>" style="width: 100%" placeholder="Comment on this...">
|
||||||
<button class="btn-Action">Post</button>
|
<button class="btn-Action">Post</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
|
|
||||||
<div class="content misc-page single-Col txt-Center" style="padding-top: 20px;">
|
<div class="content misc-page single-Col txt-Center" style="padding-top: 20px;">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-100">
|
||||||
|
<a href="/admin/reports">Site Reports</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% if flash.keys.length > 0 %>
|
<% if flash.keys.length > 0 %>
|
||||||
<div class="alert alert-error alert-block">
|
<div class="alert alert-error alert-block">
|
||||||
<% flash.keys.each do |key| %>
|
<% flash.keys.each do |key| %>
|
||||||
|
|
49
views/admin/email.erb
Normal file
49
views/admin/email.erb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="header-Outro">
|
||||||
|
<div class="row content single-Col">
|
||||||
|
<h1>Email Mass Send</h1>
|
||||||
|
<h2 class="subtitle">Use wisely.</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content misc-page single-Col txt-Center" style="padding-top: 20px;">
|
||||||
|
|
||||||
|
<% if flash.keys.length > 0 %>
|
||||||
|
<div class="alert alert-error alert-block">
|
||||||
|
<% flash.keys.each do |key| %>
|
||||||
|
<%== flash[key] %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-100">
|
||||||
|
<p>
|
||||||
|
This sends to all emails on Neocities that have not opted out. It trickles it out, 4000/day to prevent spam report issues.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="POST" action="/admin/email">
|
||||||
|
<%== csrf_token_input_html %>
|
||||||
|
<p>
|
||||||
|
Subject:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input name="subject" type="text" style="width: 600px">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Body:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<textarea name="body" style="width: 600px" rows="10"></textarea>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="submit" value="Send" class="btn">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
45
views/admin/reports.erb
Normal file
45
views/admin/reports.erb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<div class="header-Outro">
|
||||||
|
<div class="row content single-Col">
|
||||||
|
<h1>Site Reports</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content" style="background: white">
|
||||||
|
<form method="POST" action="/admin/report">
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Site</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Comments</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
<% @reports.each do |report| %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="<%= report.site.uri %>"><%= report.site.title %></a>
|
||||||
|
<br>
|
||||||
|
<img src="<%= report.site.screenshot_url('/index.html', '540x405') %>">
|
||||||
|
<br>
|
||||||
|
Reported <%= report.created_at.ago %>
|
||||||
|
<% if report.reporting_site %>
|
||||||
|
by <a href="<%= report.reporting_site.uri %>"><%= report.reporting_site.username %></a>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td><%= report.type %></td>
|
||||||
|
<td><%= report.comments[0...100] %></td>
|
||||||
|
<td>
|
||||||
|
<select name="sites[<%= report.site_id %>]">
|
||||||
|
<option value="">No Action</option>
|
||||||
|
<option value="ban">Ban Site</option>
|
||||||
|
<option value="nsfw">Mark NSFW</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<input type="submit" value="Perform Actions" class="btn">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
|
@ -12,19 +12,23 @@
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
<div class="col col-100">
|
<div class="col col-100">
|
||||||
<% if params[:tag] %>
|
<% if params[:tag] %>
|
||||||
<h1>Sites tagged <%= params[:tag] %></h1>
|
<h1><a href="/browse">Websites</a> > <%= params[:tag] %></h1>
|
||||||
<% else %>
|
<% else %>
|
||||||
<h1>Sites on Neocities</h1>
|
<h1>Websites on Neocities</h1>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="search_criteria" action="/browse" method="GET">
|
<form id="search_criteria" action="/browse" method="GET">
|
||||||
<div class="col col-50 filter">
|
<div class="col col-50 filter">
|
||||||
<fieldset class="grouping">
|
<fieldset class="grouping">
|
||||||
|
<% unless is_education? %>
|
||||||
<label class="text-Label" for="sort_by">Sort by:</label>
|
<label class="text-Label" for="sort_by">Sort by:</label>
|
||||||
<div class="select-Container">
|
<div class="select-Container">
|
||||||
<select name="sort_by" id="sort_by" class="input-Select">
|
<select name="sort_by" id="sort_by" class="input-Select">
|
||||||
|
<option value="followers" <%= params[:sort_by] == 'followers' ? 'selected' : '' %>>Most Followed</option>
|
||||||
<option value="last_updated" <%= params[:sort_by] == 'last_updated' ? 'selected' : '' %>>Last Updated</option>
|
<option value="last_updated" <%= params[:sort_by] == 'last_updated' ? 'selected' : '' %>>Last Updated</option>
|
||||||
|
<option value="supporters" <%= params[:sort_by] == 'supporters' ? 'selected' : '' %>>Neocities Supporters</option>
|
||||||
|
<option value="featured" <%= params[:sort_by] == 'featured' ? 'selected' : '' %>>Featured</option>
|
||||||
<option value="views" <%= params[:sort_by] == 'views' ? 'selected' : '' %>>Most Views</option>
|
<option value="views" <%= params[:sort_by] == 'views' ? 'selected' : '' %>>Most Views</option>
|
||||||
<option value="hits" <%= params[:sort_by] == 'hits' ? 'selected' : '' %>>Most Hits</option>
|
<option value="hits" <%= params[:sort_by] == 'hits' ? 'selected' : '' %>>Most Hits</option>
|
||||||
<option value="newest" <%= params[:sort_by] == 'newest' ? 'selected' : '' %>>Newest</option>
|
<option value="newest" <%= params[:sort_by] == 'newest' ? 'selected' : '' %>>Newest</option>
|
||||||
|
@ -32,20 +36,24 @@
|
||||||
<option value="random" <%= params[:sort_by] == 'random' ? 'selected' : '' %>>Random</option>
|
<option value="random" <%= params[:sort_by] == 'random' ? 'selected' : '' %>>Random</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<div>
|
<div>
|
||||||
<input name="is_nsfw" type="checkbox" value="true" <%= params[:is_nsfw].to_s == 'true' ? 'checked' : '' %>> Show 18+ content
|
<input name="is_nsfw" type="checkbox" value="true" <%= params[:is_nsfw].to_s == 'true' ? 'checked' : '' %>> Show 18+ content
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
<input class="btn-Action" type="submit" value="Update">
|
<input class="btn-Action" type="submit" value="Update">
|
||||||
|
<% end %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-50 filter">
|
<div class="col col-50 filter">
|
||||||
<form method="GET" action="browse">
|
<form method="GET" action="browse">
|
||||||
<fieldset class="grouping">
|
<fieldset class="grouping">
|
||||||
<label class="text-Label" for="tag"><span class="hide-on-mobile">Search by </span>Tag:</label>
|
<% unless is_education? || params[:sort_by] == 'followers' %>
|
||||||
|
<label class="text-Label" for="tag"><span class="hide-on-mobile">Filter by </span>Tag:</label>
|
||||||
<input class="input-Area typeahead" id="tag" name="tag" type="text" placeholder="pokemon" value="<%= params[:tag] %>" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" dir="auto">
|
<input class="input-Area typeahead" id="tag" name="tag" type="text" placeholder="pokemon" value="<%= params[:tag] %>" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" dir="auto">
|
||||||
<input style="vertical-align: -4px;margin-left: 4px;" type="submit" class="btn-Action" value="Search">
|
<input style="vertical-align: -4px;margin-left: 4px;" type="submit" class="btn-Action" value="Filter">
|
||||||
|
<% end %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,7 +149,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="row website-Gallery content int-Gall tags">
|
<% unless is_education? %>
|
||||||
|
<div class="row content misc">
|
||||||
<h3>Popular Tags</h3>
|
<h3>Popular Tags</h3>
|
||||||
<p>
|
<p>
|
||||||
<% Tag.popular_names(100).each do |tag| %>
|
<% Tag.popular_names(100).each do |tag| %>
|
||||||
|
@ -149,4 +158,26 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row content misc">
|
||||||
|
<h3>Text Search</h3>
|
||||||
|
<form id="searchForm" method="GET" action="https://duckduckgo.com" class="content" onsubmit="return addSiteToSearch()">
|
||||||
|
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||||
|
<fieldset>
|
||||||
|
<input id="searchQuery" name="q" type="text" placeholder="keywords" class="input-Area" autocapitalize="off" autocorrect="off" value="<%= flash[:username] %>" style="width: 50%">
|
||||||
|
<input class="btn btn-Action" type="submit" value="Search">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<p>Search powered by <a href="https://duckduckgo.com/">Duck Duck Go</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addSiteToSearch() {
|
||||||
|
var searchQuery = $('#searchQuery')
|
||||||
|
var finalSearchQuery = searchQuery.val() + ' site:neocities.org'
|
||||||
|
|
||||||
|
window.location = 'https://duckduckgo.com/?q='+encodeURI(finalSearchQuery)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<h2>Contact</h2>
|
<h2>Contact</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Please note that we can only respond to messages sent in the English language.
|
Please note that we can only respond to english inquiries.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
<div class="header-Outro with-site-image dashboard">
|
<div class="header-Outro with-site-image dashboard">
|
||||||
<div class="row content wide">
|
<div class="row content wide">
|
||||||
|
|
||||||
<div class="col col-50 signup-Area">
|
<div class="col col-50 signup-Area">
|
||||||
<div class="signup-Form">
|
<div class="signup-Form">
|
||||||
<fieldset class="content">
|
<fieldset class="content">
|
||||||
|
@ -30,7 +29,7 @@
|
||||||
<div class="col col-50">
|
<div class="col col-50">
|
||||||
<h2 class="eps"><%= current_site.title %></h2>
|
<h2 class="eps"><%= current_site.title %></h2>
|
||||||
<p class="site-url">
|
<p class="site-url">
|
||||||
<a href="//<%= current_site.host %>" target="_blank"><%= current_site.host %></a>
|
<a href="<%= current_site.uri %>" target="_blank"><%= current_site.host %></a>
|
||||||
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site} %>'><i class="fa fa-share-alt"></i> <span>Share</span></a>
|
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site} %>'><i class="fa fa-share-alt"></i> <span>Share</span></a>
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -39,7 +38,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<li>Using <strong><%= current_site.space_percentage_used %>% (<%= current_site.total_space_used.to_space_pretty %>) of your <%= current_site.maximum_space.to_space_pretty %></strong>.
|
<li>Using <strong><%= current_site.space_percentage_used %>% (<%= current_site.total_space_used.to_space_pretty %>) of your <%= current_site.maximum_space.to_space_pretty %></strong>.
|
||||||
<br>
|
<br>
|
||||||
<% if !current_site.supporter? %>Need more space? <a href="/plan">Become a Supporter!</a><% end %></li>
|
<% unless current_site.is_education || current_site.supporter? %>Need more space? <a href="/plan">Become a Supporter!</a><% end %></li>
|
||||||
<li><strong><%= current_site.views.format_large_number %></strong> views</li>
|
<li><strong><%= current_site.views.format_large_number %></strong> views</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,7 +50,7 @@
|
||||||
|
|
||||||
<div class="content wide">
|
<div class="content wide">
|
||||||
|
|
||||||
<% unless current_site.changed_count > 5 %>
|
<% unless current_site.changed_count >= 1 %>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<!-- <div class="close-button"></div> -->
|
<!-- <div class="close-button"></div> -->
|
||||||
<h4>Hello! Welcome to your new site.</h4>
|
<h4>Hello! Welcome to your new site.</h4>
|
||||||
|
@ -75,7 +74,11 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="files">
|
<div id="filesDisplay" class="files">
|
||||||
|
<script>
|
||||||
|
if(localStorage && localStorage.getItem('viewType') == 'list')
|
||||||
|
$('#filesDisplay').addClass('list-view')
|
||||||
|
</script>
|
||||||
<div id="uploadingOverlay" class="uploading-overlay" style="display: none">
|
<div id="uploadingOverlay" class="uploading-overlay" style="display: none">
|
||||||
<div class="uploading">
|
<div class="uploading">
|
||||||
<p>Uploading, please wait...</p>
|
<p>Uploading, please wait...</p>
|
||||||
|
@ -83,6 +86,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
<div class="btn-group" role="group" aria-label="...">
|
||||||
|
<button type="button" class="btn btn-default iconview-button" title="Icon View" onclick="iconView()"><i class="fa fa-th"></i></button>
|
||||||
|
<button type="button" class="btn btn-default listview-button" title="List View" onclick="listView()"><i class="fa fa-list"></i></button>
|
||||||
|
</div>
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<% if params[:dir].nil? || params[:dir].empty? || params[:dir] == '/' %>
|
<% if params[:dir].nil? || params[:dir].empty? || params[:dir] == '/' %>
|
||||||
Home
|
Home
|
||||||
|
@ -102,7 +109,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/site_files/new_page?dir=<%= Rack::Utils.escape @dir %>" class="btn-Action"><i class="fa fa-file"></i> New Page</a>
|
<a href="#createFile" class="btn-Action" data-toggle="modal"><i class="fa fa-file"></i> New File</a>
|
||||||
<a href="#createDir" class="btn-Action" data-toggle="modal"><i class="fa fa-folder"></i> New Folder</a>
|
<a href="#createDir" class="btn-Action" data-toggle="modal"><i class="fa fa-folder"></i> New Folder</a>
|
||||||
<a href="#" class="btn-Action" onclick="clickUploadFiles(); return false"><i class="fa fa-arrow-circle-up"></i> Upload</a>
|
<a href="#" class="btn-Action" onclick="clickUploadFiles(); return false"><i class="fa fa-arrow-circle-up"></i> Upload</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,6 +122,7 @@
|
||||||
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||||
<% @file_list.each do |file| %>
|
<% @file_list.each do |file| %>
|
||||||
<div class="file filehover">
|
<div class="file filehover">
|
||||||
|
<!-- <input type="checkbox" name="" value="" /> -->
|
||||||
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
|
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
|
||||||
<div class="html-thumbnail html fileimagehover">
|
<div class="html-thumbnail html fileimagehover">
|
||||||
<img src="<%= current_site.screenshot_url(file[:path], '210x158') %>" alt="">
|
<img src="<%= current_site.screenshot_url(file[:path], '210x158') %>" alt="">
|
||||||
|
@ -138,12 +146,19 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<a class="title">
|
<a class="title">
|
||||||
<% if file[:name].length > 19 %>
|
|
||||||
<%= file[:name].slice(0..18) %>…
|
|
||||||
<% else %>
|
|
||||||
<%= file[:name] %>
|
<%= file[:name] %>
|
||||||
<% end %>
|
|
||||||
</a>
|
</a>
|
||||||
|
<div class="column size">
|
||||||
|
<% if file[:size] %>
|
||||||
|
<%= file[:size].to_bytes_pretty %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="column updated">
|
||||||
|
<% if file[:updated_at] %>
|
||||||
|
<%= file[:updated_at].ago %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="overlay">
|
<div class="overlay">
|
||||||
<% if file[:is_editable] %>
|
<% if file[:is_editable] %>
|
||||||
<a href="/site_files/text_editor<%= file[:path] %>"><i class="fa fa-edit" title="Edit"></i> Edit</a>
|
<a href="/site_files/text_editor<%= file[:path] %>"><i class="fa fa-edit" title="Edit"></i> Edit</a>
|
||||||
|
@ -189,8 +204,10 @@
|
||||||
<% if !current_site.plan_feature(:no_file_restrictions) %>
|
<% if !current_site.plan_feature(:no_file_restrictions) %>
|
||||||
<a href="/site_files/allowed_types">Allowed file types</a> |
|
<a href="/site_files/allowed_types">Allowed file types</a> |
|
||||||
<% end %>
|
<% end %>
|
||||||
<a href="/site_files/<%= current_site.username %>.zip">Download entire site</a> |
|
<a href="/site_files/<%= current_site.username %>.zip">Download entire site</a>
|
||||||
<a href="/site_files/mount_info">Mount your site as a drive on your computer!</a>
|
<% unless is_education? %>
|
||||||
|
| <a href="/site_files/mount_info">Mount your site as a drive on your computer!</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -203,6 +220,44 @@
|
||||||
<input id="uploadFiles" type="file" name="files[]" multiple onchange="$('#uploadFilesButtonForm').submit()">
|
<input id="uploadFiles" type="file" name="files[]" multiple onchange="$('#uploadFilesButtonForm').submit()">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal hide fade" id="createDir" tabindex="-1" role="dialog" aria-labelledby="createDirLabel" aria-hidden="true">
|
||||||
|
<form method="post" action="/site/create_directory">
|
||||||
|
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
|
||||||
|
<input type="hidden" value="<%= @dir %>" name="dir">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
|
||||||
|
<h3 id="createDirLabel">Create Folder</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input id="newDirInput" name="name" type="text" placeholder="folder_name">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button type="submit" class="btn-Action">Create</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hide fade" id="createFile" tabindex="-1" role="dialog" aria-labelledby="createFileLabel" aria-hidden="true">
|
||||||
|
<form method="post" action="/site_files/create">
|
||||||
|
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
|
||||||
|
<input type="hidden" value="<%= @dir %>" name="dir">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
|
||||||
|
<h3 id="createFileLabel">Create New File</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input id="newFileInput" name="filename" type="text" placeholder="newfile.html">
|
||||||
|
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button type="submit" class="btn-Action">Create</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/dropzone.min.js"></script>
|
<script src="/js/dropzone.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
@ -246,8 +301,12 @@
|
||||||
|
|
||||||
this.on("error", function(file, errorMessage) {
|
this.on("error", function(file, errorMessage) {
|
||||||
hideUploadProgress()
|
hideUploadProgress()
|
||||||
|
// Guess a directory upload error
|
||||||
|
if(file.status == 'error' && file.name.match(/.+\..+/) == null && errorMessage == 'Server responded with 0 code.') {
|
||||||
|
alert('Recursive directory upload is only supported by the Chrome web browser.')
|
||||||
|
} else {
|
||||||
location.href = '/dashboard<%= @dir ? "?dir=#{@dir}" : "" %>'
|
location.href = '/dashboard<%= @dir ? "?dir=#{@dir}" : "" %>'
|
||||||
// alert('Failed: '+errorMessage)
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
||||||
|
@ -258,24 +317,32 @@
|
||||||
$('#progressBar').css('display', 'block')
|
$('#progressBar').css('display', 'block')
|
||||||
$('#uploadingProgress').css('width', progress+'%')
|
$('#uploadingProgress').css('width', progress+'%')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.on("sending", function(file) {
|
||||||
|
$('#uploads').append('<input type="hidden" name="file_paths[]" value="'+file.fullPath+'">')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('#createDir').on('shown', function () {
|
||||||
|
$('#newDirInput').focus();
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#createFile').on('shown', function () {
|
||||||
|
$('#newFileInput').focus();
|
||||||
|
})
|
||||||
|
|
||||||
|
function listView() {
|
||||||
|
if(localStorage)
|
||||||
|
localStorage.setItem('viewType', 'list')
|
||||||
|
|
||||||
|
$('#filesDisplay').addClass('list-view')
|
||||||
|
}
|
||||||
|
|
||||||
|
function iconView() {
|
||||||
|
if(localStorage)
|
||||||
|
localStorage.removeItem('viewType')
|
||||||
|
|
||||||
|
$('#filesDisplay').removeClass('list-view')
|
||||||
|
}
|
||||||
</script>
|
</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"><i class="fa fa-times"></i></button>
|
|
||||||
<h3 id="createDirLabel">Create Folder</h3>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<input name="name" type="text" placeholder="folder_name">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
|
||||||
<button type="submit" class="btn-Action">Create</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
240
views/education.erb
Normal file
240
views/education.erb
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
<body class="hp education">
|
||||||
|
<a id="new"></a>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
|
||||||
|
<header class="header-Base" role="banner">
|
||||||
|
<nav class="header-Nav clearfix" role="navigation">
|
||||||
|
<a href="#!" title="show small screen nav" class="small-Nav">
|
||||||
|
<img src="/img/nav-Icon.png" alt="navigation icon" />
|
||||||
|
</a>
|
||||||
|
<ul class="h-Nav constant-Nav" role="presentation">
|
||||||
|
<%== erb :'_header_links', layout: false %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="status-Nav">
|
||||||
|
|
||||||
|
<% if !signed_in? %>
|
||||||
|
<li>
|
||||||
|
<a href="/signin" class="sign-In">Sign In</a>
|
||||||
|
</li>
|
||||||
|
<% else %>
|
||||||
|
<li>
|
||||||
|
<a href="/dashboard" class="sign-In">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/settings" class="sign-In">Settings</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/signout" class="sign-In">Sign Out</a>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<% if flash.keys.length > 0 %>
|
||||||
|
<div class="alert alert-block txt-Center">
|
||||||
|
<% flash.keys.each do |key| %>
|
||||||
|
<%== flash[key] %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="int-Logo hp-Logo">
|
||||||
|
<a href="/" title="back to home">
|
||||||
|
<span class="hidden">Neocities.org</span>
|
||||||
|
<img src="/img/cat.png" alt="Neocities.org" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="header-Intro">
|
||||||
|
<h1 class="logo header-Content content">
|
||||||
|
<span class="hidden">Neocities for Education</span>
|
||||||
|
<img src="/img/neocities-logo-education.png" alt="Neocities.org" />
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="header-Outro">
|
||||||
|
<div class="row header-Content content">
|
||||||
|
<div class="col intro">
|
||||||
|
<h3 class="delta">A great place to learn how to build websites</h2>
|
||||||
|
<img src="/img/heartcat.png" class="float-Right">
|
||||||
|
<p class="intro-text">Completely free and continuously under development, we're building Neocities into a simple and intuitive web hosting service for students learning HTML and CSS for the first time. Give us a try and <a href="/contact">let us know</a> what you think!</p>
|
||||||
|
|
||||||
|
<h3 class="delta">Tools for creation and review</h2>
|
||||||
|
<img src="/img/about-neocities.png" class="float-Left">
|
||||||
|
<p class="intro-text">Neocities has a great built-in HTML editor and drag-and-drop upload. Instructors can easily review all class websites using our class tag feature. By signing up using this education page, students will get an experience tailored for them, and they'll only see sites from their class in the gallery.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col signup-Area">
|
||||||
|
<% if signed_in? %>
|
||||||
|
|
||||||
|
<div class="signup-Form">
|
||||||
|
<div class="content">
|
||||||
|
<h3 class="gamma txt-Center">Build your Website!</h3>
|
||||||
|
</div>
|
||||||
|
<p class="txt-Center">
|
||||||
|
Go to your dashboard to<br> start editing your website!
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<div class="txt-Center">
|
||||||
|
<a href="/dashboard" class="btn-Action">Get Started</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<form id="createSiteForm" class="signup-Form" onsubmit="return false">
|
||||||
|
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
|
||||||
|
<input type="hidden" name="is_education" value="true">
|
||||||
|
<fieldset class="content">
|
||||||
|
<h2 class="gamma">Class Sign Up</h2>
|
||||||
|
<hr />
|
||||||
|
<div class="siteCreateInputs">
|
||||||
|
<label for="create-Input">User Name</label>
|
||||||
|
<input type="text" name="prevent_autofill_username" id="prevent_autofill_username" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
||||||
|
<input type="password" name="prevent_autofill_password" id="prevent_autofill_password" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
||||||
|
<input type="text" class="input-Area" id="create-Input" name="username" placeholder="my-site-name" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||||
|
<label for="create-Input" id="domain-name">.neocities.org</label>
|
||||||
|
|
||||||
|
<label for="tags-input">Class Tag</label>
|
||||||
|
<input type="text" class="input-Area" id="tags-input" name="new_tags_string" placeholder="E.g. SmithSummer2015" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||||
|
|
||||||
|
<div class="col col-50" style="padding-left:0;">
|
||||||
|
<label for="password-input">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input type="password" class="input-Area" id="password-input"
|
||||||
|
name="password" placeholder="password"
|
||||||
|
data-placement="left" data-trigger="manual"
|
||||||
|
autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-50">
|
||||||
|
<label for="email-input">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input type="email" class="input-Area"
|
||||||
|
id="email-input" name="email"
|
||||||
|
placeholder="me@example.com" data-placement="left"
|
||||||
|
data-trigger="manual" autocapitalize="off"
|
||||||
|
autocorrect="off" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-50" style="padding-left:0;">
|
||||||
|
<label>
|
||||||
|
Confirm you are human
|
||||||
|
</label>
|
||||||
|
<div id="captcha-input" class="g-recaptcha"
|
||||||
|
data-sitekey="<%= $config['recaptcha_public_key'] %>"
|
||||||
|
data-theme="dark" data-placement="left" data-trigger="manual">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-50">
|
||||||
|
<div style="margin-top: 15px">
|
||||||
|
<input type="submit" value="Create My Site" class="btn-Action float-Right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
</div> <!-- end .col-50 -->
|
||||||
|
|
||||||
|
</div> <!-- end .row -->
|
||||||
|
|
||||||
|
</div> <!-- end .header-Outro -->
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content-Base">
|
||||||
|
|
||||||
|
<div class="section instructor-quotes">
|
||||||
|
<h2 class="delta">What Instructors Say</h2>
|
||||||
|
<div class="row content">
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="image" style="width:130px;height:130px;background:#aaa"></div>
|
||||||
|
<h3>Instructor Name<br>
|
||||||
|
Location<br>
|
||||||
|
Class name</h3>
|
||||||
|
|
||||||
|
<p>"Neocities was an excellent resource for my students - it made everything very easy.
|
||||||
|
The Neocities team did a great job responding to any questions I had."</p>
|
||||||
|
</div>
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="image" style="width:130px;height:130px;background:#aaa"></div>
|
||||||
|
<h3>Instructor Name<br>
|
||||||
|
Location<br>
|
||||||
|
Class name</h3>
|
||||||
|
|
||||||
|
<p>"Neocities was an excellent resource for my students - it made everything very easy.
|
||||||
|
The Neocities team did a great job responding to any questions I had."</p>
|
||||||
|
</div>
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="image" style="width:130px;height:130px;background:#aaa"></div>
|
||||||
|
<h3>Instructor Name<br>
|
||||||
|
Location<br>
|
||||||
|
Class name</h3>
|
||||||
|
|
||||||
|
<p>"Neocities was an excellent resource for my students - it made everything very easy.
|
||||||
|
The Neocities team did a great job responding to any questions I had."</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section support">
|
||||||
|
<h2>Support Us</h2>
|
||||||
|
<div class="row quote">
|
||||||
|
<div class="col" style="">
|
||||||
|
<p>Neocities is funded directly by our community through supporter plans and donations. We will never sell users' personal data or embed advertising on member sites. Your support allows us to pay for server costs and continue working on Neocities full-time. You can support us by making a <a href="/donate">one-time donation</a> or by <a href="/plan">subscribing for $5/month</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer-Base" role="contentinfo">
|
||||||
|
<div class="footer-Intro">
|
||||||
|
<div class="footer-Content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="f-Col f-Col-1 clearfix">
|
||||||
|
<span class="footer-icon"></span>
|
||||||
|
<h2 class="delta">Support Us</h2>
|
||||||
|
<p class="tiny">
|
||||||
|
Neocities is funded by <a href="/plan">supporters</a> and <a href="/donate">donations</a>. If you’d like to contribute, you can help us pay our server costs using credit card, Bitcoin, or PayPal.
|
||||||
|
</p>
|
||||||
|
<a href="/donate" title="Donate to Neocities" class="action-Link">Donate Today</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="f-Col f-Col-2 clearfix">
|
||||||
|
<span class="footer-icon"></span>
|
||||||
|
<h2 class="delta">About Us</h2>
|
||||||
|
<p class="tiny">
|
||||||
|
Neocities is here to bring back the creativity and
|
||||||
|
free expression to the world wide web that made it great.
|
||||||
|
</p>
|
||||||
|
<a href="/about" title="More about Neocities" class="action-Link">More About Us</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="f-Col f-Col-3 clearfix">
|
||||||
|
<span class="footer-icon"></span>
|
||||||
|
<h2 class="delta">Latest News</h2>
|
||||||
|
<p class="tiny">
|
||||||
|
The latest news on Neocities can be found on our blog.
|
||||||
|
</p>
|
||||||
|
<a href="/blog" title="Read about Neocities news and updates" class="action-Link">Read More</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%== erb :'_footer', layout: false %>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<%== erb :'_index_signup_script', layout: false %>
|
||||||
|
</body>
|
|
@ -21,7 +21,18 @@
|
||||||
<div class="content misc-page columns right-col">
|
<div class="content misc-page columns right-col">
|
||||||
<div class="col-left">
|
<div class="col-left">
|
||||||
<div class="col col-66">
|
<div class="col col-66">
|
||||||
<% if site.followings_dataset.count == 0 %>
|
<div class="welcome kickstarter">
|
||||||
|
<h4><a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">The Neocities Kickstarter: An HTML/CSS course for everyone</a></h4>
|
||||||
|
<p><strong><%= kickstarter_days_remaining %> days left</strong> to get Neocities rewards and support our education goals! <a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">Learn More >></a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if site.site_changed == false || site.changed_count == 0 %>
|
||||||
|
<div class="welcome">
|
||||||
|
<h4>Thanks for joining the Neocities community!</h4>
|
||||||
|
<p>Now start <a href="/dashboard">building your website</a>!</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% elsif site.followings_dataset.count == 0 %>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<h4>Welcome to your Neocities news feed!</h4>
|
<h4>Welcome to your Neocities news feed!</h4>
|
||||||
<p>
|
<p>
|
||||||
|
@ -32,14 +43,6 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if !site.site_changed && site.followings_dataset.count > 0 %>
|
|
||||||
<div class="welcome">
|
|
||||||
<h4>Thanks for joining the Neocities community!</h4>
|
|
||||||
<p>Now start <a href="/dashboard">building your website</a>!</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if !@events.empty? %>
|
<% if !@events.empty? %>
|
||||||
<%== erb :'_news', layout: false, locals: {site: current_site, events: @events} %>
|
<%== erb :'_news', layout: false, locals: {site: current_site, events: @events} %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -87,7 +90,7 @@
|
||||||
|
|
||||||
<div class="col col-33">
|
<div class="col col-33">
|
||||||
<div class="news-site-info">
|
<div class="news-site-info">
|
||||||
<p class="site-url"><a href="//<%= current_site.host %>" target="_blank"><%= site.title %></a></p>
|
<p class="site-url"><a href="<%= current_site.uri %>" target="_blank"><%= site.title %></a></p>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="col col-50">
|
<div class="col col-50">
|
||||||
<% if site.updated_at %>
|
<% if site.updated_at %>
|
||||||
|
@ -104,7 +107,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="//<%= site.host %>" class="large-portrait" style="background-image:url(<%= site.screenshot_url('index.html', '540x405') %>);"></a>
|
<a href="<%= site.uri %>" class="large-portrait" style="background-image:url(<%= site.screenshot_url('index.html', '540x405') %>);"></a>
|
||||||
|
|
||||||
<div class="news-profile-button">
|
<div class="news-profile-button">
|
||||||
<a href="/site/<%= site.username %>" class="btn-Action"><i class="fa fa-user"></i> Profile</a>
|
<a href="/site/<%= site.username %>" class="btn-Action"><i class="fa fa-user"></i> Profile</a>
|
||||||
|
|
125
views/index.erb
125
views/index.erb
|
@ -1,45 +1,3 @@
|
||||||
<!doctype html>
|
|
||||||
<!--[if IE 8 ]><html lang="en" class="ieAll ie8"><![endif]-->
|
|
||||||
<!--[if IE 9 ]><html lang="en" class="ieAll ie9"><![endif]-->
|
|
||||||
<!--[if (gt IE 9)|!(IE)]><!--><html lang="en"><!--<![endif]-->
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
|
|
||||||
<title>Neocities: Create your free website now!</title>
|
|
||||||
<meta itemprop="name" content="Neocities.org" />
|
|
||||||
<meta itemprop="description" content="Create your own free home page, and do whatever you want with it." />
|
|
||||||
<meta name="description" content="Neocities is the new Geocities. Create your own free home page, and do whatever you want with it." />
|
|
||||||
<meta name="keywords" content="free website, html, css, learn to code, free hosting, build a website, create a web page" />
|
|
||||||
|
|
||||||
<link rel="canonical" href="//neocities.org" />
|
|
||||||
|
|
||||||
<meta property="og:title" content="Neocities"/>
|
|
||||||
<meta property="og:site_name" content="Neocities | neocities.org"/>
|
|
||||||
<meta property="og:type" content="website"/>
|
|
||||||
<meta property="og:image" content=""/>
|
|
||||||
<meta property="og:url" content="//www.neocities.org"/>
|
|
||||||
<meta property="og:description" content="Neocities is the new Geocities. Create your own free home page, and do whatever you want with it."/>
|
|
||||||
|
|
||||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico?v=4" />
|
|
||||||
<link rel="apple-touch-icon-precomposed" href="#apple-icon-144.png" />
|
|
||||||
<link rel="apple-touch-startup-image" href="#startup.png" />
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Mobile Meta -->
|
|
||||||
<meta name="HandheldFriendly" content="True" />
|
|
||||||
<meta name="MobileOptimized" content="320" />
|
|
||||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
|
|
||||||
|
|
||||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
|
|
||||||
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script type="text/javascript" src="/js/html5.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
<script src="/js/jquery-1.11.0.min.js"></script>
|
|
||||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="hp">
|
<body class="hp">
|
||||||
<a id="new"></a>
|
<a id="new"></a>
|
||||||
|
|
||||||
|
@ -75,12 +33,10 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<% if flash.keys.length > 0 %>
|
<% if flash.keys.length > 0 %>
|
||||||
<div class="alert txt-Center">
|
<div class="alert alert-block txt-Center">
|
||||||
<p style="padding:5px">
|
|
||||||
<% flash.keys.each do |key| %>
|
<% flash.keys.each do |key| %>
|
||||||
<%== flash[key] %>
|
<%== flash[key] %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
@ -101,16 +57,31 @@
|
||||||
<div class="header-Outro">
|
<div class="header-Outro">
|
||||||
<div class="row header-Content content">
|
<div class="row header-Content content">
|
||||||
<div class="col intro">
|
<div class="col intro">
|
||||||
<h2 class="section-header">Create your own free web site, and discover new ones.</h2>
|
<h2 class="section-header">Create your own free web site. Unlimited creativity, zero ads.</h2>
|
||||||
<p class="intro-text">
|
<p class="intro-text">
|
||||||
Neocities is a community of <a href="/browse"><%= @sites_count.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %> sites</a> that are bringing back the lost individual creativity of the web. We provide free hosting and tools that allow anyone to make a web site. Only your imagination is required. Join us!
|
Neocities is a community of <a href="/browse"><%= @sites_count.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %> sites</a> that are bringing back the lost individual creativity of the web. We provide free hosting and tools that allow anyone to make a web site. Only your imagination is required. Join us!
|
||||||
</p>
|
</p>
|
||||||
<ul class="intro-List">
|
<div class="intro-List kickstarter">
|
||||||
|
<div class="col col-40">
|
||||||
|
<a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev"><img src="/img/kickstarterthumbnail.jpg" style="width: 100%; float: left; margin-right: 20px; border: 3px solid white;margin-bottom: 1em"></a>
|
||||||
|
</div>
|
||||||
|
<div class="col col-60">
|
||||||
|
<div class="title">
|
||||||
|
<a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">
|
||||||
|
SUPPORT OUR
|
||||||
|
<img src="/img/kickstarterlogo.png" style="width: 280px; display:block; margin: 11px 0px;">
|
||||||
|
<%= kickstarter_days_remaining %> DAYS LEFT
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p>We need your help to build an amazing HTML/CSS course into Neocities! <a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">Learn More >></a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--<ul class="intro-List">
|
||||||
<li class="intro-Social">
|
<li class="intro-Social">
|
||||||
<span class="intro-Icon"></span>
|
<span class="intro-Icon"></span>
|
||||||
<h3 class="delta">Share your web creation with the world</h3>
|
<h3 class="delta">Share your web creation with the world</h3>
|
||||||
<p class="base">
|
<p class="base">
|
||||||
Follow your favorite Neocities sites to keep up with all their latest updates. Discover new websites related to your interests using tags, comment on them, and share them. Unlimited creativity, zero ads.
|
Follow your favorite Neocities sites to keep up with all their latest updates. Discover new websites related to your interests using tags, comment on them, and share them.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li class="intro-Tools">
|
<li class="intro-Tools">
|
||||||
|
@ -119,7 +90,7 @@
|
||||||
<p class="base">
|
<p class="base">
|
||||||
We’ve made it easier to build your website and explore other sites. Neocities features an in-browser HTML editor, custom domain support, faster site loading, easy file uploading, RSS feeds, folder support, and much more.
|
We’ve made it easier to build your website and explore other sites. Neocities features an in-browser HTML editor, custom domain support, faster site loading, easy file uploading, RSS feeds, folder support, and much more.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col signup-Area">
|
<div class="col signup-Area">
|
||||||
|
@ -140,6 +111,7 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<form id="createSiteForm" class="signup-Form" onsubmit="return false">
|
<form id="createSiteForm" class="signup-Form" onsubmit="return false">
|
||||||
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
|
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
|
||||||
|
<input type="hidden" name="is_education" value="false">
|
||||||
<fieldset class="content">
|
<fieldset class="content">
|
||||||
<h2 class="gamma">Sign up for free</h2>
|
<h2 class="gamma">Sign up for free</h2>
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -232,17 +204,17 @@
|
||||||
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-editor-screenshot.png)"></div></div>
|
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-editor-screenshot.png)"></div></div>
|
||||||
<div class="col col-50 text">
|
<div class="col col-50 text">
|
||||||
<h3><i class="fa fa-edit"></i> HTML editor, right in your browser</h3>
|
<h3><i class="fa fa-edit"></i> HTML editor, right in your browser</h3>
|
||||||
<p>No tools needed! With our easy-to-use HTML editor, you're ready to start building your awesome web site right now.</p>
|
<p>No tools needed. With our easy-to-use HTML editor, you're ready to start building your awesome web site right now.</p>
|
||||||
<p>If you'd rather use your favorite desktop editor, no problem! Uploading files is as easy as drag-and-drop. We also support WebDAV uploading for <a href="/plan">supporter accounts</a>.</p>
|
<p>If you'd rather use your favorite desktop editor, no problem. Uploading files is as easy as drag-n-drop.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row content right">
|
<div class="row content right">
|
||||||
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-browse-screenshot.png);"></div></div>
|
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-browse-screenshot.png);"></div></div>
|
||||||
<div class="col col-50 text">
|
<div class="col col-50 text">
|
||||||
<h3><i class="fa fa-globe"></i> It's time to bring back web surfing!</h3>
|
<h3><i class="fa fa-globe"></i> It's time to bring back web surfing.</h3>
|
||||||
<p>We collect all Neocities sites in our <a href="/browse">website gallery</a>. We make it easy to browse sites with our optional surf bar.</p>
|
<p>All Neocities sites are viewable in our <a href="/browse">website gallery</a>. And it's easy to browse sites with our optional surf bar.</p>
|
||||||
<p>Using tags, our version of web rings, you can easily discover new sites related to your interests.</p>
|
<p>Using tags (our version of Web Rings) you can easily discover new sites related to your interests.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -250,7 +222,7 @@
|
||||||
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-follow-screenshot.png);"></div></div>
|
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-follow-screenshot.png);"></div></div>
|
||||||
<div class="col col-50 text">
|
<div class="col col-50 text">
|
||||||
<h3><i class="fa fa-user-plus"></i> Follow your favorite Neocities sites</h3>
|
<h3><i class="fa fa-user-plus"></i> Follow your favorite Neocities sites</h3>
|
||||||
<p>Once you find some interesting sites in our website gallery, you can keep track of all new updates by following them. Any changes to the sites show up in your news feed. You'll also see what sites are followed by your favorite site builders.</p>
|
<p>Keep track of all your favorite sites by following them. Any changes to the sites automatically show up in your news feed. You'll also see what sites they follow.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -258,7 +230,7 @@
|
||||||
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-comment-screenshot.png);"></div></div>
|
<div class="col col-50"><div class="screenshot" style="background-image:url(/img/front-comment-screenshot.png);"></div></div>
|
||||||
<div class="col col-50 text">
|
<div class="col col-50 text">
|
||||||
<h3><i class="fa fa-comments-o"></i> Web creativity plus community</h3>
|
<h3><i class="fa fa-comments-o"></i> Web creativity plus community</h3>
|
||||||
<p>Interact with your favorite web builders by posting comments, liking updates, and sharing their sites on your social network of choice!</p>
|
<p>Interact with your favorite web builders by posting comments, and sharing their sites on your social network of choice.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -358,47 +330,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%== erb :'_footer', layout: false %>
|
<%== erb :'_footer', layout: false %>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script src="/js/app.min.js"></script>
|
<%== erb :'_index_signup_script', layout: false %>
|
||||||
<script src="/js/bootstrap.min.js"></script>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
$('#createSiteForm').on('submit', function(obj) {
|
|
||||||
$.post('/create_validate_all', $(obj.target).serialize(), function(errors) {
|
|
||||||
if(errors.length == 0) {
|
|
||||||
$.post('/create', $('#createSiteForm').serialize(), function(res) {
|
|
||||||
window.location.href = '/welcome'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for(var i=0; i<errors.length;i++) {
|
|
||||||
if(errors[i][0] == 'captcha') {
|
|
||||||
var captchaDiv = $('#captcha-input')
|
|
||||||
captchaDiv.attr('data-original-title', errors[i][1])
|
|
||||||
captchaDiv.tooltip('show')
|
|
||||||
} else {
|
|
||||||
var ele = $('input[name='+errors[i][0]+']')
|
|
||||||
ele.attr('data-original-title', errors[i][1])
|
|
||||||
ele.tooltip('show')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
$('input[type=text],input[type=password]').on('change focusout', function(obj) {
|
|
||||||
$.post('/create_validate', {field: obj.target.name, value: obj.target.value, csrf_token: '<%= csrf_token %>'}, function(res) {
|
|
||||||
if(res.result == 'ok') {
|
|
||||||
return $(obj.target).tooltip('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
$(obj.target).attr('data-original-title', res.error)
|
|
||||||
$(obj.target).tooltip('show')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
|
44
views/index_layout.erb
Normal file
44
views/index_layout.erb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!doctype html>
|
||||||
|
<!--[if IE 8 ]><html lang="en" class="ieAll ie8"><![endif]-->
|
||||||
|
<!--[if IE 9 ]><html lang="en" class="ieAll ie9"><![endif]-->
|
||||||
|
<!--[if (gt IE 9)|!(IE)]><!--><html lang="en"><!--<![endif]-->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
|
||||||
|
<title>Neocities: Create your free website now!</title>
|
||||||
|
<meta itemprop="name" content="Neocities" />
|
||||||
|
<meta itemprop="description" content="Create your own free web site, and do whatever you want with it." />
|
||||||
|
<meta name="description" content="Create your own free web site, and do whatever you want with it." />
|
||||||
|
<meta name="keywords" content="free website, html, css, learn to code, free hosting, build a website, create a web page" />
|
||||||
|
|
||||||
|
<link rel="canonical" href="//neocities.org" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="Neocities"/>
|
||||||
|
<meta property="og:site_name" content="Neocities | neocities.org"/>
|
||||||
|
<meta property="og:type" content="website"/>
|
||||||
|
<meta property="og:image" content=""/>
|
||||||
|
<meta property="og:url" content="//www.neocities.org"/>
|
||||||
|
<meta property="og:description" content="Create your own free web site, and do whatever you want with it."/>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/ico" href="/favicon.ico?v=4" />
|
||||||
|
<link rel="apple-touch-icon-precomposed" href="#apple-icon-144.png" />
|
||||||
|
<link rel="apple-touch-startup-image" href="#startup.png" />
|
||||||
|
|
||||||
|
<!-- Mobile Meta -->
|
||||||
|
<meta name="HandheldFriendly" content="True" />
|
||||||
|
<meta name="MobileOptimized" content="320" />
|
||||||
|
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
|
||||||
|
|
||||||
|
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
|
||||||
|
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script type="text/javascript" src="/js/html5.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<script src="/js/jquery-1.11.0.min.js"></script>
|
||||||
|
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<%== yield %>
|
||||||
|
|
||||||
|
</html>
|
35
views/permanent_web.erb
Normal file
35
views/permanent_web.erb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="header-Outro">
|
||||||
|
<div class="row content single-Col">
|
||||||
|
<h1>Neocities and the Permanent Web</h1>
|
||||||
|
<h2 class="subtitle">Working to build a faster, better, more permanent web.</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content single-Col misc-page">
|
||||||
|
<img src="/img/neocities-ipfs.jpg" style="margin-bottom: 20px">
|
||||||
|
<article role="article">
|
||||||
|
<p>
|
||||||
|
Neocities has launched an experimental implementation of <a href="http://ipfs.io">IPFS</a>. IPFS is short for the "Inter-Planetary File System", and is the foundation for a new way to distribute web content that is being called The Permanent Web. The idea behind the Permanent Web is simple: Instead of serving web sites from central servers, we believe that web serving should be decentralized, and that IPFS is an eventual replacement to the aging HTTP protocol.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is still very early stage technology, so if you don't understand what this stuff is, don't worry about it for now. But if you'd like to read more about why we're interested in this new technology, please see our <a href="#">blog post</a> on the topic.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
IPFS archiving and downloading is now supported by <strong>all web sites</strong> on Neocities. You'll see an IPFS hash link on the site profile, and an archive link that allows you to see past versions of your site (note: this is still a preview, so past site archives may still disappear, but we're working on making it better).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you want to play around with this new technology, you can get IPFS for your computer and use it to retreive content from our IPFS node servers. All you need to do is <a href="http://ipfs.io/docs/install/">download the IPFS daemon</a> (OSX/Linux only for now), and run the following command in your terminal:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<code>$ ipfs pin add -r THE_IPFS_HASH_FOR_YOUR_SITE</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We have a lot of very interesting projects we're working on with IPFS that will make Neocities even better. Stay tuned for some interesting new technology over the next year!
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
152
views/plan/_compare.erb
Normal file
152
views/plan/_compare.erb
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
<h2 style="margin-top: 2em">What You Get</h2>
|
||||||
|
<table class="plan-chart">
|
||||||
|
<tr>
|
||||||
|
<th class="feature-column"></th>
|
||||||
|
<th>
|
||||||
|
<h3>Free</h3>
|
||||||
|
<% if request.path != '/welcome' %>
|
||||||
|
<p>$<%= Site::PLAN_FEATURES[:free][:price] %>/mo</p>
|
||||||
|
<%== plan_pricing_button :free %>
|
||||||
|
<% end %>
|
||||||
|
</th>
|
||||||
|
<th class="professional">
|
||||||
|
<h3>Supporter</h3>
|
||||||
|
<% if request.path != '/welcome' %>
|
||||||
|
<% if parent_site && parent_site.legacy_supporter? %>
|
||||||
|
<p>$<%= Site::LEGACY_SUPPORTER_PRICES[parent_site.values[:plan_type].to_sym] %>/mo</p>
|
||||||
|
<div class="current-plan">Current Plan</div>
|
||||||
|
<% else %>
|
||||||
|
<p>$<%= Site::PLAN_FEATURES[:supporter][:price] %>/mo</p>
|
||||||
|
<%== plan_pricing_button :supporter %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="How much disk space you can use for your site. More space means you can upload more files.">Storage</span>
|
||||||
|
</td>
|
||||||
|
<td><%= Site::PLAN_FEATURES[:free][:space].to_bytes_pretty %></td>
|
||||||
|
<td><%= (Site::PLAN_FEATURES[:supporter][:space] / (10**6)).to_comma_separated %> MB</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="How much content you can serve in one month. Don’t worry—these are soft limits. Temporary surges are fine, we won't take your site down automatically, and we're very flexible.">Bandwidth</span>
|
||||||
|
</td>
|
||||||
|
<td><%= Site::PLAN_FEATURES[:free][:bandwidth].to_bytes_pretty %></td>
|
||||||
|
<td><%= Site::PLAN_FEATURES[:supporter][:bandwidth].to_bytes_pretty %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="We will never put ads on your site, or give your data to marketers. In fact, we’re prohibited from doing it in our own Terms of Service.">No advertising, ever</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Our worldwide network of CDN servers cache your web site regionally, making your site load fast—no matter where your users are. 頑張る, 日本!">Global CDN Site Cache</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Each site comes with a free Neocities address (yoursitename.neocities.org).">Free Site Subdomain</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Downloading your neocities site is as easy as clicking a button.">One-Click Backups</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Site tags make it easy to find sites you’re interested in, and join communities of sites. It’s like a web ring, but even better.">Site Tags</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Each site has its own RSS feed that make it easy to let people know when your site changes.">RSS Feed</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Like a site on Neocities? Now you can follow it, and see when they update!">Follow Your Favorite Sites</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Each site now has its own profile page, allowing users to see when your site updates, follow your site, and leave feedback.">Site Profile</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Our HTML5 site editor makes it easy to make quick changes to your site, even if you’re on the run.">Neocities Site Editor</span>
|
||||||
|
</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<!--
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">Site Tipping (Coming Soon)</td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
-->
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="You can mount your Neocities site to your computer as a hard drive, making it easy to update with your favorite HTML editor.">WebDAV Uploading</span>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Add your domain name (yoursite.com) to your site!">Custom Domains</span>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Make as many sites as you want with one account, and easily switch between them.">Unlimited Site Creation</span>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="Upload your SSL certificate for your custom domain name, preventing snoops from seeing your user’s traffic.">Custom SSL Certs (soon)</span>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="feature-column">
|
||||||
|
<span data-original-title="No whitelists—you can upload anything you want (no copyrighted content, trojans, or w4r3z, please).">No File Upload Type Restrictions</span>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td><i class="fa fa-check"></i></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
$('.feature-column span').tooltip({
|
||||||
|
placement: 'right'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -1,31 +1,5 @@
|
||||||
<%
|
<%
|
||||||
def plan_pricing_button(plan_type)
|
|
||||||
plan_type = plan_type.to_s
|
|
||||||
|
|
||||||
if !parent_site
|
|
||||||
%{<a href="/#new" class="btn-Action">Sign Up</a>}
|
|
||||||
elsif parent_site && parent_site.plan_type == plan_type
|
|
||||||
if request.path.match /\/welcome/
|
|
||||||
%{<a href="/" class="btn-Action">Get Started</a>}
|
|
||||||
else
|
|
||||||
%{<div class="current-plan">Current Plan</div>}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
#if plan_type == 'supporter'
|
|
||||||
# plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]*12}, once per year"
|
|
||||||
#else
|
|
||||||
plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]}, monthly"
|
|
||||||
#end
|
|
||||||
|
|
||||||
if request.path.match /\/welcome/
|
|
||||||
button_title = 'Get Started'
|
|
||||||
else
|
|
||||||
button_title = parent_site.plan_type == 'free' ? 'Upgrade' : 'Change'
|
|
||||||
end
|
|
||||||
|
|
||||||
%{<a data-plan_name="#{Site::PLAN_FEATURES[plan_type.to_sym][:name]}" data-plan_type="#{plan_type}" data-plan_price="#{plan_price}" onclick="card = new Skeuocard($('#skeuocard')); return false" class="btn-Action planPricingButton">#{button_title}</a>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<section class="section plans">
|
<section class="section plans">
|
||||||
|
@ -125,147 +99,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <h2><a href="">Compare Plans <i class="fa fa-caret-down"></i></a></h2> -->
|
<%== erb :'plan/_compare', layout: false %>
|
||||||
<h2 style="margin-top: 2em">Compare</h2>
|
|
||||||
<table class="plan-chart">
|
|
||||||
<tr>
|
|
||||||
<th class="feature-column"></th>
|
|
||||||
<th>
|
|
||||||
<h3>Free</h3>
|
|
||||||
<p>$<%= Site::PLAN_FEATURES[:free][:price] %>/mo</p>
|
|
||||||
<%== plan_pricing_button :free %>
|
|
||||||
</th>
|
|
||||||
<th class="professional">
|
|
||||||
<h3>Supporter</h3>
|
|
||||||
<% if parent_site && parent_site.legacy_supporter? %>
|
|
||||||
<p>$<%= Site::LEGACY_SUPPORTER_PRICES[parent_site.values[:plan_type].to_sym] %>/mo</p>
|
|
||||||
<div class="current-plan">Current Plan</div>
|
|
||||||
<% else %>
|
|
||||||
<p>$<%= Site::PLAN_FEATURES[:supporter][:price] %>/mo</p>
|
|
||||||
<%== plan_pricing_button :supporter %>
|
|
||||||
<% end %>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="How much disk space you can use for your site. More space means you can upload more files.">Storage</span>
|
|
||||||
</td>
|
|
||||||
<td><%= Site::PLAN_FEATURES[:free][:space].to_bytes_pretty %></td>
|
|
||||||
<td><%= Site::PLAN_FEATURES[:supporter][:space].to_bytes_pretty %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="How much content you can serve in one month. Don’t worry—these are soft limits. Temporary surges are fine, we won't take your site down automatically, and we're very flexible.">Bandwidth</span>
|
|
||||||
</td>
|
|
||||||
<td><%= Site::PLAN_FEATURES[:free][:bandwidth].to_bytes_pretty %></td>
|
|
||||||
<td>1000 GB</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="We will never put ads on your site, or give your data to marketers. In fact, we’re prohibited from doing it in our own Terms of Service.">No advertising, ever</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Our worldwide network of CDN servers cache your web site regionally, making your site load fast—no matter where your users are. 頑張る, 日本!">Global CDN Site Cache</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Each site comes with a free Neocities address (yoursitename.neocities.org).">Free Site Subdomain</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Downloading your neocities site is as easy as clicking a button.">One-Click Backups</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Site tags make it easy to find sites you’re interested in, and join communities of sites. It’s like a web ring, but even better.">Site Tags</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Each site has its own RSS feed that make it easy to let people know when your site changes.">RSS Feed</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Like a site on Neocities? Now you can follow it, and see when they update!">Follow Your Favorite Sites</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Each site now has its own profile page, allowing users to see when your site updates, follow your site, and leave feedback.">Site Profile</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Our HTML5 site editor makes it easy to make quick changes to your site, even if you’re on the run.">Neocities Site Editor</span>
|
|
||||||
</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<!--
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">Site Tipping (Coming Soon)</td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
-->
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="You can mount your Neocities site to your computer as a hard drive, making it easy to update with your favorite HTML editor.">WebDAV Uploading</span>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Add your domain name (yoursite.com) to your site!">Custom Domains</span>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Make as many sites as you want with one account, and easily switch between them.">Unlimited Site Creation</span>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="Upload your SSL certificate for your custom domain name, preventing snoops from seeing your user’s traffic.">Custom SSL Certs (soon)</span>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="feature-column">
|
|
||||||
<span data-original-title="No whitelists—you can upload anything you want (no copyrighted content, trojans, or w4r3z, please).">No File Upload Type Restrictions</span>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td><i class="fa fa-check"></i></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<% if request.path.match /\/plan/ %>
|
<% if request.path.match /\/plan/ %>
|
||||||
<div class="row" style="margin-top: 50px; margin-bottom: 0px;">
|
<div class="row" style="margin-top: 50px; margin-bottom: 0px;">
|
||||||
|
@ -300,7 +134,7 @@
|
||||||
<strong>It's safe.</strong> We use <a href="https://stripe.com" target="_blank">Stripe</a> for payments, and never store your credit card information directly.
|
<strong>It's safe.</strong> We use <a href="https://stripe.com" target="_blank">Stripe</a> for payments, and never store your credit card information directly.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>It's affordable.</strong> As low as <strong>$<%= Site::PLAN_FEATURES[:supporter][:price] %>/month</strong> (billed once every year). Higher tiers are optional (and appreciated!)
|
<strong>It's affordable.</strong> Only $<%= Site::PLAN_FEATURES[:supporter][:price] %> per month.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>You can cancel or change plans anytime.</strong> If you do, we'll refund or credit the amount you didn't use.
|
<strong>You can cancel or change plans anytime.</strong> If you do, we'll refund or credit the amount you didn't use.
|
||||||
|
@ -313,10 +147,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
$(function() {
|
||||||
$('.feature-column span').tooltip({
|
|
||||||
placement: 'right'
|
|
||||||
})
|
|
||||||
|
|
||||||
$('.plan-image').tooltip({
|
$('.plan-image').tooltip({
|
||||||
placement: 'top'
|
placement: 'top'
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||||
<input type="hidden" name="cmd" value="_s-xclick">
|
<input type="hidden" name="cmd" value="_s-xclick">
|
||||||
<input type="hidden" name="hosted_button_id" value="RVN4HCVY4FT9Y">
|
<input type="hidden" name="hosted_button_id" value="DFQBR5LNS3NW8">
|
||||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribeCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribeCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -14,6 +14,16 @@
|
||||||
<div class="col col-66" style="min-height: 43em;">
|
<div class="col col-66" style="min-height: 43em;">
|
||||||
<h2>Latest News</h2>
|
<h2>Latest News</h2>
|
||||||
<div class="press-news">
|
<div class="press-news">
|
||||||
|
<h3><a href="http://recode.net/2015/07/17/why-we-all-need-to-make-the-internet-fun-again/"><strong>Re/code</strong>:
|
||||||
|
Why We All Need to Make the Internet Fun Again</a></h3>
|
||||||
|
<blockquote>
|
||||||
|
In a word, the Internet has become boring. When it went mass market in the mid-’90s, the Web was promised as a place of open exploration and creativity. Now, instead, it restricts our activity at nearly every turn. This doesn’t just constrain us as people, but threatens to impede the very inventiveness that the Internet industry depends on to continue thriving. What’s needed now is an understanding of how we reached this point — and an alternative vision for the Internet’s next generation.
|
||||||
|
</blockquote>
|
||||||
|
<br>
|
||||||
|
<blockquote>
|
||||||
|
Fortunately, we are starting to see a strong movement away from the templated, uniform Internet. Instead of defining the limits of their identity and expressiveness through social media, [people] have already turned en masse to indie games like Minecraft (bought by Microsoft for $2.5 billion last year), a free-form, online sandbox world, with building tools that enable them to build everything from massive 3-D cities to working computers and continent-spanning roller coasters. [Neocities], a quasi-rebirth of GeoCities and a vanguard member of the independent Web movement, has seen enormous growth since launching in 2013, with nearly 50,000 websites created by its users.
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
<h3><a href="http://www.royalgazette.com/article/20150103/NEWS/150109957"><strong>The Royal Gazette</strong>: Students learn to be junior web wizards</a></h3>
|
<h3><a href="http://www.royalgazette.com/article/20150103/NEWS/150109957"><strong>The Royal Gazette</strong>: Students learn to be junior web wizards</a></h3>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
About 60 students attended the Island’s first Hackathon yesterday to learn about computer coding and building their own websites. One of the Hackathon organisers, James Tucker, said: “Some of the students have been able to take what they learned here and move that from the Codecademy site and into a real webpage, so they have now got a presence on the web, which is their own thing that they produced themselves.” The budding programmers were able to get their partial websites online for free, using the free web-hosting system Neocities.
|
About 60 students attended the Island’s first Hackathon yesterday to learn about computer coding and building their own websites. One of the Hackathon organisers, James Tucker, said: “Some of the students have been able to take what they learned here and move that from the Codecademy site and into a real webpage, so they have now got a presence on the web, which is their own thing that they produced themselves.” The budding programmers were able to get their partial websites online for free, using the free web-hosting system Neocities.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
You will have to purchase a domain name from a registrar like <a href="https://www.namecheap.com/?aff=53678" target="_blank">Namecheap</a> first. We are working on providing domain purchasing from Neocities in the future, but in general it is best if you own the domain, because then you control your site.
|
You will have to purchase a domain name from a registrar like <a href="http://www.namecheap.com/?aff=87835" target="_blank">Namecheap</a> first. We are working on providing domain purchasing from Neocities in the future, but in general it is best if you own the domain, because then you control your site.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if current_site.custom_domain_available? %>
|
<% if current_site.custom_domain_available? %>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<h3>Step 1</h3>
|
<h3>Step 1</h3>
|
||||||
<p>
|
<p>
|
||||||
First, you need to add an "A record" to point your root domain (catsknitting.com) to the following IP address:
|
First, you need to add an "A record" to point your root domain (sometimes shown with an @ symbol) (catsknitting.com) to the following IP address:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p><code>54.68.34.66</code></p>
|
<p><code>54.68.34.66</code></p>
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
<div class="col col-50 profile-info">
|
<div class="col col-50 profile-info">
|
||||||
<h2 class="eps title-with-badge"><span><%= site.title %></span> <% if site.supporter? %><a href="/plan" class="supporter-badge" title="Neocities Supporter"></a> <% end %></h2>
|
<h2 class="eps title-with-badge"><span><%= site.title %></span> <% if site.supporter? %><a href="/plan" class="supporter-badge" title="Neocities Supporter"></a> <% end %></h2>
|
||||||
<p class="site-url"><a href="<%= site.uri %>"><%= site.host %></a></p>
|
<p class="site-url"><a href="<%= site.uri %>"><%= site.host %></a></p>
|
||||||
|
<!--
|
||||||
|
<% if site.latest_archive %>
|
||||||
|
<p><a href="<%= site.latest_archive.url %>" style="margin-right: 5px">IPFS Link</a><small style="font-size: 7pt"><a href="/permanent-web">(what is this?)</a></small></p>
|
||||||
|
<% end %>
|
||||||
|
-->
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat"><strong><%= site.views.format_large_number %></strong> <span>view<%= site.views == 1 ? '' : 's' %></span></div>
|
<div class="stat"><strong><%= site.views.format_large_number %></strong> <span>view<%= site.views == 1 ? '' : 's' %></span></div>
|
||||||
<% follows_count = site.follows_dataset.count %>
|
<% follows_count = site.follows_dataset.count %>
|
||||||
|
@ -33,6 +38,12 @@
|
||||||
<a href="/dashboard" class="btn-Action edit"><i class="fa fa-edit" title="Edit"></i> Edit Site</a>
|
<a href="/dashboard" class="btn-Action edit"><i class="fa fa-edit" title="Edit"></i> Edit Site</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<% if current_site && current_site.id == site.id && site.latest_archive %>
|
||||||
|
<a href="/site/<%= site.username %>/archives" class="btn-Action edit"><i class="fa fa-history" title="Archives"></i> Archives</a>
|
||||||
|
<% end %>
|
||||||
|
-->
|
||||||
|
|
||||||
<% if current_site && current_site != site %>
|
<% if current_site && current_site != site %>
|
||||||
<% is_following = current_site.is_following?(site) %>
|
<% is_following = current_site.is_following?(site) %>
|
||||||
|
|
||||||
|
@ -109,6 +120,7 @@
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat"><span>Created</span><strong><%= site.created_at.strftime('%b %-d, %Y') %></strong></div>
|
<div class="stat"><span>Created</span><strong><%= site.created_at.strftime('%b %-d, %Y') %></strong></div>
|
||||||
|
<a href="/site/<%= site.username %>/stats">Site Traffic Stats</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%== erb :'_follows', layout: false, locals: {site: site, is_current_site: site == current_site} %>
|
<%== erb :'_follows', layout: false, locals: {site: site, is_current_site: site == current_site} %>
|
||||||
|
|
30
views/site/archives.erb
Normal file
30
views/site/archives.erb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="header-Outro">
|
||||||
|
<div class="row content single-Col">
|
||||||
|
<h1>Permanent Web Archives</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content single-Col misc-page">
|
||||||
|
<article role="article">
|
||||||
|
<% if @archives.length == 0 %>
|
||||||
|
No archives yet.
|
||||||
|
<% else %>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>IPFS Hash <small style="display: inline"><a href="/permanent-web">(what is this?)</a></small></th>
|
||||||
|
<th>Archived Time</th>
|
||||||
|
</tr>
|
||||||
|
<% @archives.each do |archive| %>
|
||||||
|
<tr>
|
||||||
|
<td><a href="<%= archive.url %>"><%= archive.ipfs_hash %></a></td>
|
||||||
|
<td><%= archive.updated_at.ago.downcase %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Note: This is a very early preview release of a new technology! We're still figuring things out. We may stop hosting archives without notice. <a href="/permanent-web">Learn how you can host your own copies of these archives</a>.
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</article>
|
||||||
|
</div>
|
342
views/site/stats.erb
Normal file
342
views/site/stats.erb
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
|
||||||
|
<div class="header-Outro with-columns">
|
||||||
|
<div class="row content">
|
||||||
|
<div class="col col-100">
|
||||||
|
<h3>Site Statistics</h3>
|
||||||
|
|
||||||
|
<div class="feed-filter">
|
||||||
|
<% if !@events.empty? && (site.followings_dataset.count > 0) %>
|
||||||
|
<a href="/" <% if params[:activity].nil? %>class="selected"<% end %>>All</a>
|
||||||
|
<a href="/?activity=mine" <% if params[:activity] == 'mine' %>class="selected"<% end %>>Profile Activity</a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container news-feed">
|
||||||
|
<div class="content misc-page columns right-col">
|
||||||
|
<div class="col-left">
|
||||||
|
<div class="col col-66">
|
||||||
|
<!--
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col col-100 globe">
|
||||||
|
<div id="earth_div"></div>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<div class="col col-50" style="padding-right: 0;">
|
||||||
|
<table class="table table-striped" id="latest-visitors">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">San Francisco, CA</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> neocities.org</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Portland, OR</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">London, UK</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> Twitter URL</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Hong Kong, China</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">San Francisco, CA</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> Facebook URL</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">San Francisco, CA</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> neocities.org</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Portland, OR</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">London, UK</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> Twitter URL</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Hong Kong, China</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a>, <a href="">/tech</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Hong Kong, China</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<h2>Total Visitors <small>last 7 days</small></h2>
|
||||||
|
|
||||||
|
<% if current_site && current_site.id == @site.id %>
|
||||||
|
|
||||||
|
<% if current_site.supporter? %>
|
||||||
|
<ul class="nav h-Nav">
|
||||||
|
<li><a href="?days=30">30 days</a></li>
|
||||||
|
<li><a href="?days=90">90 days</a></li>
|
||||||
|
<li><a href="?days=365">1 year</a></li>
|
||||||
|
<li><a href="?days=sincethebigbang">All time</a></li>
|
||||||
|
</ul>
|
||||||
|
<% else %>
|
||||||
|
<p>(<a href="/plan">Upgrade</a> to see up to see stats for all time)</p>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<canvas id="myChart" style="width:100%;height:300px;display:block"></canvas>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-50">
|
||||||
|
<h2>Top Paths <small>last 7 days</small></h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @stats[:paths].each do |path| %>
|
||||||
|
<tr>
|
||||||
|
<td><a href="<%= @site.uri+path.name %>" target="_blank"><%= path.name.gsub(/\?.+/i, '') %></a></td>
|
||||||
|
<td><%= path.views %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col col-50">
|
||||||
|
<h2>Top Locations <small>last 7 days</small></h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>City</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @stats[:locations].each do |location| %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<%= location[:name] %>
|
||||||
|
</td>
|
||||||
|
<td><%= location[:views] %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if current_site && current_site.id == @site.id %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-100">
|
||||||
|
<h2>Top Referrers</h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Referrer</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @stats[:referrers].each do |referrer| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= referrer.url %></td>
|
||||||
|
<td><%= referrer.views %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="news-site-info">
|
||||||
|
<p class="site-url"><a href="<%= @site.uri %>" target="_blank"><%= @site.title %></a></p>
|
||||||
|
<div class="stats">
|
||||||
|
<div class="col col-50">
|
||||||
|
<% if site.updated_at %>
|
||||||
|
Last updated<br><strong><%= site.updated_at.ago %></strong>
|
||||||
|
<% else %>
|
||||||
|
Your new website!<br><strong><a href="/dashboard"><i class="fa fa-edit" title="Edit"></i> Start Building</a></strong>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="col col-50">
|
||||||
|
<div><strong><%= site.views.format_large_number %></strong> views</div>
|
||||||
|
<% follows_count = site.follows_dataset.count %>
|
||||||
|
<div><strong><%= follows_count.format_large_number %></strong> follower<%= follows_count == 1 ? '' : 's' %></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="<%= site.uri %>" class="large-portrait" style="background-image:url(<%= site.screenshot_url('index.html', '540x405') %>);"></a>
|
||||||
|
|
||||||
|
<div class="news-profile-button">
|
||||||
|
<a href="/site/<%= site.username %>" class="btn-Action"><i class="fa fa-user"></i> Profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <script src="//www.webglearth.com/v2/api.js"></script> -->
|
||||||
|
<script src="/js/Chart.min.js"></script>
|
||||||
|
<script>
|
||||||
|
//OpenGL globe
|
||||||
|
$(document).ready(function() {
|
||||||
|
/*
|
||||||
|
var options = {
|
||||||
|
sky: true,
|
||||||
|
atmosphere: false,
|
||||||
|
dragging: true,
|
||||||
|
tilting: true,
|
||||||
|
center: [46.8011, 8.2266],
|
||||||
|
zoom: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var earth = new WE.map('earth_div', options)
|
||||||
|
earth.setView([20, -100], 2.07)
|
||||||
|
|
||||||
|
// WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
||||||
|
// attribution: '© OpenStreetMap'
|
||||||
|
//}).addTo(earth);
|
||||||
|
|
||||||
|
WE.tileLayer('http://data.webglearth.com/natural-earth-color/{z}/{x}/{y}.jpg', {
|
||||||
|
tileSize: 256,
|
||||||
|
bounds: [[-85, -180], [85, 180]],
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 16,
|
||||||
|
attribution: 'WebGL Earth Tiles',
|
||||||
|
tms: true
|
||||||
|
}).addTo(earth)
|
||||||
|
|
||||||
|
<% @stats[:locations].each do |location| %>
|
||||||
|
var marker = WE.marker([<%= location[:latitude] %>, <%= location[:longitude] %>]).addTo(earth);
|
||||||
|
marker.bindPopup("<b><%= location[:name] %></b><br><%= location[:views] %> views", {maxWidth: 150, closeButton: true})
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Start a simple rotation animation
|
||||||
|
var before = null
|
||||||
|
requestAnimationFrame(function animate(now) {
|
||||||
|
var c = earth.getPosition()
|
||||||
|
var elapsed = before? now - before: 0
|
||||||
|
before = now
|
||||||
|
earth.setCenter([c[0], c[1] + 0.1*(elapsed/30)])
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
//chart.js
|
||||||
|
var data = {
|
||||||
|
labels: <%== @stats[:stat_days].collect {|s| s.created_at.strftime("%b %-d")}.to_json %>,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Hits",
|
||||||
|
fillColor: "rgba(220,220,220,0.2)",
|
||||||
|
strokeColor: "rgba(220,220,220,1)",
|
||||||
|
pointColor: "rgba(220,220,220,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||||
|
data: <%== @stats[:stat_days].collect {|s| s.hits}.to_json %>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Unique Visits",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: <%== @stats[:stat_days].collect {|s| s.views}.to_json %>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// Get context with jQuery - using jQuery's .get() method.
|
||||||
|
var ctx = $("#myChart").get(0).getContext("2d")
|
||||||
|
// This will get the first returned node in the jQuery collection.
|
||||||
|
//var myNewChart = new Chart(ctx);
|
||||||
|
var myLineChart = new Chart(ctx).Line(data, {
|
||||||
|
bezierCurve: false,
|
||||||
|
multiTooltipTemplate: "<%== @multi_tooltip_template %>"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -13,6 +13,7 @@
|
||||||
<li>JavaScript (.js, .json, .geojson)</li>
|
<li>JavaScript (.js, .json, .geojson)</li>
|
||||||
<li>CSS (.css)</li>
|
<li>CSS (.css)</li>
|
||||||
<li>Text (.txt, .text, .csv, .tsv)</li>
|
<li>Text (.txt, .text, .csv, .tsv)</li>
|
||||||
|
<li>XML (.xml)</li>
|
||||||
<li>Web Fonts (.eot, .ttf, .woff, .woff2, .svg)</li>
|
<li>Web Fonts (.eot, .ttf, .woff, .woff2, .svg)</li>
|
||||||
<li>MIDI Files (.mid, .midi)</li>
|
<li>MIDI Files (.mid, .midi)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<form method="POST" action="/site_files/create_page" enctype="multipart/form-data">
|
<form method="POST" action="/site_files/create" enctype="multipart/form-data">
|
||||||
<%== csrf_token_input_html %>
|
<%== csrf_token_input_html %>
|
||||||
<input name="dir" type="hidden" value="<%= params[:dir] %>">
|
<input name="dir" type="hidden" value="<%= params[:dir] %>">
|
||||||
<h2>What's the name of your page?</h2>
|
<h2>What's the name of your page?</h2>
|
||||||
<p><input type="text" name="pagefilename" autocapitalize="off" autocorrect="off">.html</p>
|
<p><input type="text" name="filename" autocapitalize="off" autocorrect="off">.html</p>
|
||||||
<p><input class="btn-Action" type="submit" value="Create Page"></p>
|
<p><input class="btn-Action" type="submit" value="Create Page"></p>
|
||||||
|
|
||||||
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
|
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
|
||||||
|
|
|
@ -27,7 +27,12 @@
|
||||||
<div class="header-Outro editor">
|
<div class="header-Outro editor">
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<a href="/dashboard">Dashboard</a> > <span class="filename"><%= @filename %></span>
|
<a href="/dashboard">Dashboard</a> >
|
||||||
|
<span class="filename">
|
||||||
|
<% dir_array = Pathname(@filename).dirname.to_s.split '/' %>
|
||||||
|
<% dir_array = [] if dir_array == ['.'] %>
|
||||||
|
<% dir_array.each_with_index do |dir,i| %><a href="/dashboard?dir=<%= Rack::Utils.escape dir_array[0..i].join('/') %>"><%= dir %></a>/<% end %><%= Pathname(@filename).basename %>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
<div class="theme">
|
<div class="theme">
|
||||||
|
@ -69,9 +74,11 @@
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<a id="saveButton" class="btn-Action" href="#" onclick="saveTextFile(false); return false" style="opacity: 0.5"><i class="fa fa-save"></i>Save</a>
|
<a id="saveButton" class="btn-Action" href="#" onclick="saveTextFile(false); return false" style="opacity: 0.5"><span class="hide-on-mobile"><i class="fa fa-save"></i></span>Save</a>
|
||||||
<a class="btn-Action" href="//<%= current_site.host %>/<%= @filename %>" target="_blank">View</a>
|
<span class="hide-on-mobile">
|
||||||
|
<a class="btn-Action" href="<%= current_site.uri %>/<%= @filename %>" target="_blank">View</a>
|
||||||
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site, page_uri: "#{current_site.uri}/#{@filename}"} %>'>Share</a>
|
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site, page_uri: "#{current_site.uri}/#{@filename}"} %>'>Share</a>
|
||||||
|
</span>
|
||||||
<!-- <a id="saveAndExitButton" class="btn-Action" href="#" onclick="saveTextFile(true); return false" style="opacity: 0.5"><i class="fa fa-save"></i> Save and Exit</a> -->
|
<!-- <a id="saveAndExitButton" class="btn-Action" href="#" onclick="saveTextFile(true); return false" style="opacity: 0.5"><i class="fa fa-save"></i> Save and Exit</a> -->
|
||||||
<div id="editorUpdates" class="tooltip fade bottom in hidden" style="top: 90px;right: 12.5em;">
|
<div id="editorUpdates" class="tooltip fade bottom in hidden" style="top: 90px;right: 12.5em;">
|
||||||
<div class="tooltip-arrow"></div>
|
<div class="tooltip-arrow"></div>
|
||||||
|
@ -85,8 +92,7 @@
|
||||||
|
|
||||||
<div class="row editor">
|
<div class="row editor">
|
||||||
<div class="col col-100">
|
<div class="col col-100">
|
||||||
<div id="editor"><%==encoding_fix(@file_data) %>
|
<div id="editor"><%==encoding_fix(@file_data) %></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<div class="content single-Col misc-page">
|
<div class="content single-Col misc-page">
|
||||||
<article>
|
<article>
|
||||||
<h2>Why our finances are open</h2>
|
<h2>Why our growth statistics and finances are open</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Neocities is an <a href="http://www.opencompany.org">Open Company</a> startup, and are committed to openness as a defining element in how we create value.
|
Neocities is an <a href="http://www.opencompany.org">Open Company</a> startup, and are committed to openness as a defining element in how we create value.
|
||||||
|
@ -17,11 +17,11 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Despite this, most companies today still treat their backend infrastructure and business finances as proprietary. The result is an erosion of user trust, where users become dependant on proprietary services that have bugs they can't help fix, financial sustainability problems they can't discover, and limitations they can't understand. We believe this is the biggest problem facing startups today.
|
Despite this, most companies today still treat their backend infrastructure and business operations as proprietary. The result is an erosion of user trust, where users become dependant on proprietary services that have bugs they can't help fix, financial sustainability problems they can't discover, and limitations they can't understand. We believe this is the biggest problem facing startups today.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
We do not feel that opening of our finances is as risky as is currently believed, and that the increased trust we gain from doing it more than makes up for the risk. So we have decided to open our finances.
|
We do not feel that opening of our finances is as risky as is currently believed, and that the increased trust we gain from doing it more than makes up for the risk. So we have decided to open our growth statistics and finances.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Note that we do not disclose everything publicly. For example, we do not reveal the locations of our servers, the identities of our users, investors (if they choose to be anonymous) or any salaries or dividends we choose to issue. There are very legitimate security and privacy considerations to consider when providing open data, and we understand and respect that.
|
Note that we do not disclose everything publicly. For example, we do not currently reveal the locations of our servers, the identities of our users, investors (if they choose to be anonymous) or any salaries or dividends (in our case, member ownership percentages) we choose to issue. There are very legitimate security and privacy considerations to consider when providing open data, and we understand and respect that.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -40,7 +40,15 @@
|
||||||
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Total Sites</td>
|
<td><strong>Total Hits</strong> (sites hosted on Neocities)</td>
|
||||||
|
<td><%= @stats[:total_hosted_site_hits].to_comma_separated %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Total Views</strong> (sites hosted on Neocities)</td>
|
||||||
|
<td><%= @stats[:total_hosted_site_views].to_comma_separated %></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Total Sites</strong></td>
|
||||||
<td><%= @stats[:total_sites] %></td>
|
<td><%= @stats[:total_sites] %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
342
views/stats_mockup.erb
Normal file
342
views/stats_mockup.erb
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
|
||||||
|
|
||||||
|
<div class="header-Outro with-columns">
|
||||||
|
<div class="row content">
|
||||||
|
<div class="col col-66">
|
||||||
|
<h3>Your Stats</h3>
|
||||||
|
<div class="feed-filter">
|
||||||
|
<% if !@events.empty? && (site.followings_dataset.count > 0) %>
|
||||||
|
<a href="/" <% if params[:activity].nil? %>class="selected"<% end %>>All</a>
|
||||||
|
<a href="/?activity=mine" <% if params[:activity] == 'mine' %>class="selected"<% end %>>Profile Activity</a>
|
||||||
|
<% end %>
|
||||||
|
<a href="/activity">Global Activity</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-32">
|
||||||
|
<h3>Your Site</h3>
|
||||||
|
<a href="/dashboard" class="btn-Action edit"><i class="fa fa-edit" title="Edit"></i>Edit Site</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container news-feed">
|
||||||
|
<div class="content misc-page columns right-col">
|
||||||
|
<div class="col-left">
|
||||||
|
<div class="col col-66">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col col-50 globe">
|
||||||
|
<h2>Latest Visitors</h2>
|
||||||
|
<div id="earth_div"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-50" style="padding-right: 0;">
|
||||||
|
<table class="table table-striped" id="latest-visitors">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">San Francisco, CA</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> neocities.org</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Portland, OR</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">London, UK</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> Twitter URL</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Hong Kong, China</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">San Francisco, CA</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> Facebook URL</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">San Francisco, CA</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> neocities.org</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Portland, OR</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">London, UK</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-link"></i> Twitter URL</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Hong Kong, China</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a>, <a href="">/tech</a>, <a href="">/about</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="location">Hong Kong, China</span>
|
||||||
|
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="time">7:11PM - 4/27/15</span>
|
||||||
|
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Last 7 Days</h2>
|
||||||
|
<p>(<a href="/plan">Upgrade</a> to see up to see stats for all time)</p>
|
||||||
|
<ul class="nav h-Nav">
|
||||||
|
<li><a href="">Month</a></li>
|
||||||
|
<li><a href="">3 months</a></li>
|
||||||
|
<li><a href="">1 Year</a></li>
|
||||||
|
<li><a href="">All time</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<canvas id="myChart" style="width:100%;height:300px;display:block"></canvas>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-50">
|
||||||
|
<h2>Top Paths</h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>/</td>
|
||||||
|
<td>130</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>/contact</td>
|
||||||
|
<td>110</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>/art</td>
|
||||||
|
<td>101</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>/about</td>
|
||||||
|
<td>99</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>/links</td>
|
||||||
|
<td>33</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Top Locations</h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>City</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Portland, OR, USA</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Portland, OR, USA</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Portland, OR, USA</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Portland, OR, USA</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Portland, OR, USA</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-50">
|
||||||
|
|
||||||
|
<h2>Top Referrers</h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Referrer</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Google search</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Google search</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Google search</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Google search</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Google search</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-33">
|
||||||
|
<div class="news-site-info">
|
||||||
|
<p class="site-url"><a href="<%= current_site.uri %>" target="_blank"><%= site.title %></a></p>
|
||||||
|
<div class="stats">
|
||||||
|
<div class="col col-50">
|
||||||
|
<% if site.updated_at %>
|
||||||
|
Last updated<br><strong><%= site.updated_at.ago %></strong>
|
||||||
|
<% else %>
|
||||||
|
Your new website!<br><strong><a href="/dashboard"><i class="fa fa-edit" title="Edit"></i> Start Building</a></strong>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="col col-50">
|
||||||
|
<div><strong><%= site.views.format_large_number %></strong> views</div>
|
||||||
|
<% follows_count = site.follows_dataset.count %>
|
||||||
|
<div><strong><%= follows_count.format_large_number %></strong> follower<%= follows_count == 1 ? '' : 's' %></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="<%= site.uri %>" class="large-portrait" style="background-image:url(<%= site.screenshot_url('index.html', '540x405') %>);"></a>
|
||||||
|
|
||||||
|
<div class="news-profile-button">
|
||||||
|
<a href="/site/<%= site.username %>" class="btn-Action"><i class="fa fa-user"></i> Profile</a>
|
||||||
|
<a href="#" id="shareButton" class="btn-Action" data-container="body" data-toggle="popover" data-placement="bottom" data-content='<%== erb :'_share', layout: false, locals: {site: current_site} %>'><i class="fa fa-share-alt"></i> Share</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%== erb :'_follows', layout: false, locals: {site: site, is_current_site: site == current_site} %>
|
||||||
|
|
||||||
|
<%== erb :'_tags', layout: false, locals: {site: site, is_current_site: site == current_site} %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="http://www.webglearth.com/v2/api.js"></script>
|
||||||
|
<script src="/js/Chart.min.js"></script>
|
||||||
|
<script>
|
||||||
|
//OpenGL globe
|
||||||
|
$(document).ready(function() {
|
||||||
|
var earth = new WE.map('earth_div');
|
||||||
|
earth.setView([20, -100], 2.07);
|
||||||
|
WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
||||||
|
attribution: '© OpenStreetMap'
|
||||||
|
}).addTo(earth);
|
||||||
|
|
||||||
|
// Start a simple rotation animation
|
||||||
|
var before = null;
|
||||||
|
requestAnimationFrame(function animate(now) {
|
||||||
|
var c = earth.getPosition();
|
||||||
|
var elapsed = before? now - before: 0;
|
||||||
|
before = now;
|
||||||
|
earth.setCenter([c[0], c[1] + 0.1*(elapsed/30)]);
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
});
|
||||||
|
|
||||||
|
//chart.js
|
||||||
|
var data = {
|
||||||
|
labels: ["May 1", "May 2", "May 3", "May 4", "May 5", "May 6", "May7"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Hits",
|
||||||
|
fillColor: "rgba(220,220,220,0.2)",
|
||||||
|
strokeColor: "rgba(220,220,220,1)",
|
||||||
|
pointColor: "rgba(220,220,220,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||||
|
data: [65, 59, 80, 81, 56, 55, 65]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Visits",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: [28, 48, 40, 66, 33, 27, 45]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// Get context with jQuery - using jQuery's .get() method.
|
||||||
|
var ctx = $("#myChart").get(0).getContext("2d");
|
||||||
|
// This will get the first returned node in the jQuery collection.
|
||||||
|
//var myNewChart = new Chart(ctx);
|
||||||
|
var myLineChart = new Chart(ctx).Line(data, {bezierCurve: false});
|
||||||
|
})
|
||||||
|
</script>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue