mirror of
https://github.com/neocities/neocities.git
synced 2025-04-25 01:32:36 +02:00
catch branch up with master
This commit is contained in:
commit
af0a31d6a2
102 changed files with 4055 additions and 919 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/*
|
||||||
|
|
5
Gemfile
5
Gemfile
|
@ -28,6 +28,10 @@ gem 'rack-cache'
|
||||||
gem 'rest-client', require: 'rest_client'
|
gem 'rest-client', require: 'rest_client'
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
gem 'paypal-recurring', require: 'paypal/recurring'
|
gem 'paypal-recurring', require: 'paypal/recurring'
|
||||||
|
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
|
||||||
|
@ -74,6 +78,7 @@ 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
|
||||||
|
|
269
Gemfile.lock
269
Gemfile.lock
|
@ -1,44 +1,45 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (4.1.4)
|
activesupport (4.2.3)
|
||||||
i18n (~> 0.6, >= 0.6.9)
|
i18n (~> 0.7)
|
||||||
json (~> 1.7, >= 1.7.7)
|
json (~> 1.7, >= 1.7.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.3, >= 0.3.4)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.3.7)
|
addressable (2.3.8)
|
||||||
ago (0.1.5)
|
ago (0.1.5)
|
||||||
ansi (1.4.3)
|
annoy (0.5.6)
|
||||||
|
highline (>= 1.5.0)
|
||||||
|
ansi (1.5.0)
|
||||||
autoparse (0.3.3)
|
autoparse (0.3.3)
|
||||||
addressable (>= 2.3.1)
|
addressable (>= 2.3.1)
|
||||||
extlib (>= 0.9.15)
|
extlib (>= 0.9.15)
|
||||||
multi_json (>= 1.0.0)
|
multi_json (>= 1.0.0)
|
||||||
bcrypt (3.1.7)
|
bcrypt (3.1.10)
|
||||||
blankslate (3.1.3)
|
blankslate (3.1.3)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
byebug (2.7.0)
|
byebug (4.0.5)
|
||||||
columnize (~> 0.3)
|
columnize (= 0.9.0)
|
||||||
debugger-linecache (~> 1.2)
|
capybara (2.4.4)
|
||||||
capybara (2.4.1)
|
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
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.16.0)
|
||||||
timers (~> 1.1.0)
|
timers (~> 4.0.0)
|
||||||
climate_control (0.0.3)
|
climate_control (0.0.3)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
cliver (0.3.2)
|
cliver (0.3.2)
|
||||||
cocaine (0.5.4)
|
cocaine (0.5.7)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.0)
|
coderay (1.1.0)
|
||||||
columnize (0.8.9)
|
columnize (0.9.0)
|
||||||
connection_pool (2.0.0)
|
connection_pool (2.2.0)
|
||||||
crack (0.4.2)
|
crack (0.4.2)
|
||||||
safe_yaml (~> 1.0.0)
|
safe_yaml (~> 1.0.0)
|
||||||
dante (0.2.0)
|
dante (0.2.0)
|
||||||
|
@ -46,186 +47,212 @@ GEM
|
||||||
nokogiri (>= 1.4.2)
|
nokogiri (>= 1.4.2)
|
||||||
rack (>= 1.1.0)
|
rack (>= 1.1.0)
|
||||||
uuidtools (~> 2.1.1)
|
uuidtools (~> 2.1.1)
|
||||||
debugger-linecache (1.2.0)
|
dnsruby (1.58.0)
|
||||||
docile (1.1.3)
|
docile (1.1.5)
|
||||||
domain_name (0.5.23)
|
domain_name (0.5.24)
|
||||||
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.13.2)
|
||||||
faker (1.3.0)
|
faker (1.4.3)
|
||||||
i18n (~> 0.5)
|
i18n (~> 0.5)
|
||||||
faraday (0.9.0)
|
faraday (0.9.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ffi (1.9.6)
|
ffi (1.9.10)
|
||||||
ffi-compiler (0.1.3)
|
ffi-compiler (0.1.3)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
rake
|
rake
|
||||||
filesize (0.0.3)
|
filesize (0.1.0)
|
||||||
google-api-client (0.7.1)
|
geoip (1.6.1)
|
||||||
addressable (>= 2.3.2)
|
google-api-client (0.8.6)
|
||||||
autoparse (>= 0.3.3)
|
activesupport (>= 3.2)
|
||||||
extlib (>= 0.9.15)
|
addressable (~> 2.3)
|
||||||
faraday (>= 0.9.0)
|
autoparse (~> 0.3)
|
||||||
jwt (>= 0.1.5)
|
extlib (~> 0.9)
|
||||||
launchy (>= 2.1.1)
|
faraday (~> 0.9)
|
||||||
multi_json (>= 1.0.0)
|
googleauth (~> 0.3)
|
||||||
retriable (>= 1.4)
|
launchy (~> 2.4)
|
||||||
signet (>= 0.5.0)
|
multi_json (~> 1.10)
|
||||||
uuidtools (>= 2.1.0)
|
retriable (~> 1.4)
|
||||||
hashie (2.0.5)
|
signet (~> 0.6)
|
||||||
hiredis (0.5.0)
|
googleauth (0.4.1)
|
||||||
|
faraday (~> 0.9)
|
||||||
|
jwt (~> 1.4)
|
||||||
|
logging (~> 2.0)
|
||||||
|
memoist (~> 0.12)
|
||||||
|
multi_json (= 1.11)
|
||||||
|
signet (~> 0.6)
|
||||||
|
highline (1.7.2)
|
||||||
|
hiredis (0.6.0)
|
||||||
|
hitimes (1.2.2)
|
||||||
http-cookie (1.0.2)
|
http-cookie (1.0.2)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
i18n (0.6.9)
|
i18n (0.7.0)
|
||||||
|
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)
|
||||||
rack (~> 1.4)
|
rack (~> 1.4)
|
||||||
rest-client (~> 1.0)
|
rest-client (~> 1.0)
|
||||||
json (1.8.1)
|
json (1.8.3)
|
||||||
jwt (0.1.11)
|
jwt (1.5.1)
|
||||||
multi_json (>= 1.5)
|
kgio (2.9.3)
|
||||||
kgio (2.9.2)
|
launchy (2.4.3)
|
||||||
launchy (2.4.2)
|
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
magic (0.2.6)
|
little-plugger (1.1.3)
|
||||||
|
logging (2.0.0)
|
||||||
|
little-plugger (~> 1.1)
|
||||||
|
multi_json (~> 1.10)
|
||||||
|
magic (0.2.9)
|
||||||
ffi (>= 0.6.3)
|
ffi (>= 0.6.3)
|
||||||
mail (2.5.4)
|
mail (2.6.3)
|
||||||
mime-types (~> 1.16)
|
mime-types (>= 1.16, < 3)
|
||||||
treetop (~> 1.4.8)
|
memoist (0.12.0)
|
||||||
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 (2.6.1)
|
||||||
mini_portile (0.6.0)
|
mini_portile (0.6.2)
|
||||||
minitest (5.3.1)
|
minitest (5.7.0)
|
||||||
minitest-reporters (1.0.2)
|
minitest-reporters (1.0.18)
|
||||||
ansi
|
ansi
|
||||||
builder
|
builder
|
||||||
minitest (>= 5.0)
|
minitest (>= 5.0)
|
||||||
powerbar
|
ruby-progressbar
|
||||||
mocha (1.0.0)
|
mocha (1.1.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)
|
||||||
paypal-recurring (1.1.0)
|
paypal-recurring (1.1.0)
|
||||||
pg (0.17.1)
|
pg (0.18.2)
|
||||||
phantomjs (1.9.7.1)
|
phantomjs (1.9.8.0)
|
||||||
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)
|
||||||
websocket-driver (>= 0.2.0)
|
websocket-driver (>= 0.2.0)
|
||||||
polyglot (0.3.4)
|
pry (0.10.1)
|
||||||
powerbar (1.0.11)
|
coderay (~> 1.1.0)
|
||||||
ansi (~> 1.4.0)
|
method_source (~> 0.8.1)
|
||||||
hashie (>= 1.1.0)
|
|
||||||
pry (0.9.12.6)
|
|
||||||
coderay (~> 1.0)
|
|
||||||
method_source (~> 0.8)
|
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
pry-byebug (1.3.2)
|
pry-byebug (3.1.0)
|
||||||
byebug (~> 2.7)
|
byebug (~> 4.0)
|
||||||
pry (~> 0.9.12)
|
pry (~> 0.10)
|
||||||
puma (2.8.1)
|
puma (2.11.3)
|
||||||
rack (>= 1.1, < 2.0)
|
rack (>= 1.1, < 2.0)
|
||||||
rack (1.5.2)
|
rack (1.6.4)
|
||||||
rack-cache (1.2)
|
rack-cache (1.2)
|
||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
rack-protection (1.5.2)
|
rack-protection (1.5.3)
|
||||||
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)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rainbows (4.6.1)
|
rainbows (4.6.2)
|
||||||
kgio (~> 2.5)
|
kgio (~> 2.5)
|
||||||
rack (~> 1.1)
|
rack (~> 1.1)
|
||||||
unicorn (~> 4.8)
|
unicorn (~> 4.8)
|
||||||
raindrops (0.13.0)
|
raindrops (0.14.0)
|
||||||
rake (10.3.2)
|
rake (10.4.2)
|
||||||
redis (3.0.7)
|
redis (3.2.1)
|
||||||
redis-namespace (1.4.1)
|
redis-namespace (1.5.2)
|
||||||
redis (~> 3.0.4)
|
redis (~> 3.0, >= 3.0.4)
|
||||||
rest-client (1.8.0)
|
rest-client (1.8.0)
|
||||||
http-cookie (>= 1.0.2, < 2.0)
|
http-cookie (>= 1.0.2, < 2.0)
|
||||||
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.2)
|
||||||
|
ruby-progressbar (1.7.5)
|
||||||
|
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.4.16)
|
||||||
screencap (0.1.1)
|
screencap (0.1.2)
|
||||||
phantomjs
|
phantomjs
|
||||||
scrypt (2.0.0)
|
scrypt (2.0.2)
|
||||||
ffi-compiler (>= 0.0.2)
|
ffi-compiler (>= 0.0.2)
|
||||||
rake
|
rake
|
||||||
sequel (4.8.0)
|
sequel (4.8.0)
|
||||||
sequel_pg (1.6.9)
|
sequel_pg (1.6.13)
|
||||||
pg (>= 0.8.0)
|
pg (>= 0.8.0)
|
||||||
sequel (>= 3.39.0)
|
sequel (>= 3.39.0)
|
||||||
shotgun (0.9)
|
shotgun (0.9.1)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
sidekiq (3.0.0)
|
sidekiq (3.4.1)
|
||||||
celluloid (>= 0.15.2)
|
celluloid (~> 0.16.0)
|
||||||
connection_pool (>= 2.0.0)
|
connection_pool (>= 2.1.1)
|
||||||
json
|
json
|
||||||
redis (>= 3.0.6)
|
redis (>= 3.0.6)
|
||||||
redis-namespace (>= 1.3.1)
|
redis-namespace (>= 1.3.1)
|
||||||
signet (0.5.0)
|
signet (0.6.1)
|
||||||
addressable (>= 2.2.3)
|
addressable (~> 2.3)
|
||||||
faraday (>= 0.9.0.rc5)
|
extlib (~> 0.9)
|
||||||
jwt (>= 0.1.5)
|
faraday (~> 0.9)
|
||||||
multi_json (>= 1.0.0)
|
jwt (~> 1.5)
|
||||||
simplecov (0.8.2)
|
multi_json (~> 1.10)
|
||||||
|
simplecov (0.10.0)
|
||||||
docile (~> 1.1.0)
|
docile (~> 1.1.0)
|
||||||
multi_json
|
json (~> 1.8)
|
||||||
simplecov-html (~> 0.8.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.8.0)
|
simplecov-html (0.10.0)
|
||||||
sinatra (1.4.4)
|
sinatra (1.4.6)
|
||||||
rack (~> 1.4)
|
rack (~> 1.4)
|
||||||
rack-protection (~> 1.4)
|
rack-protection (~> 1.4)
|
||||||
tilt (~> 1.3, >= 1.3.4)
|
tilt (>= 1.3, < 3)
|
||||||
sinatra-flash (0.3.0)
|
sinatra-flash (0.3.0)
|
||||||
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)
|
||||||
stripe (1.15.0)
|
storable (0.8.9)
|
||||||
|
stripe (1.23.0)
|
||||||
json (~> 1.8.1)
|
json (~> 1.8.1)
|
||||||
mime-types (>= 1.25, < 3.0)
|
|
||||||
rest-client (~> 1.4)
|
rest-client (~> 1.4)
|
||||||
stripe-ruby-mock (2.0.1)
|
stripe-ruby-mock (2.0.5)
|
||||||
dante (>= 0.2.0)
|
dante (>= 0.2.0)
|
||||||
jimson-temp
|
jimson-temp
|
||||||
stripe (>= 1.15.0)
|
stripe (>= 1.15.0)
|
||||||
thread (0.1.4)
|
sysinfo (0.8.1)
|
||||||
thread_safe (0.3.4)
|
drydock
|
||||||
tilt (1.4.1)
|
storable
|
||||||
timers (1.1.0)
|
thread (0.2.1)
|
||||||
treetop (1.4.15)
|
thread_safe (0.3.5)
|
||||||
polyglot
|
tilt (2.0.1)
|
||||||
polyglot (>= 0.3.1)
|
timecop (0.7.4)
|
||||||
|
timers (4.0.1)
|
||||||
|
hitimes
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.6)
|
unf_ext (0.0.7.1)
|
||||||
unicorn (4.8.2)
|
unicorn (4.9.0)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
rack
|
rack
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
uuidtools (2.1.4)
|
uuidtools (2.1.5)
|
||||||
webmock (1.17.4)
|
webmock (1.21.0)
|
||||||
addressable (>= 2.2.7)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
websocket-driver (0.3.4)
|
websocket-driver (0.6.1)
|
||||||
|
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)
|
||||||
|
@ -240,12 +267,15 @@ 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
|
||||||
|
@ -270,6 +300,7 @@ DEPENDENCIES
|
||||||
rest-client
|
rest-client
|
||||||
rmagick
|
rmagick
|
||||||
ruby-debug
|
ruby-debug
|
||||||
|
rye
|
||||||
sass
|
sass
|
||||||
screencap
|
screencap
|
||||||
scrypt
|
scrypt
|
||||||
|
@ -285,5 +316,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,6 +1,16 @@
|
||||||
get '/browse/?' do
|
get '/browse/?' do
|
||||||
|
@current_page = params[:current_page]
|
||||||
|
@current_page = @current_page.to_i
|
||||||
|
@current_page = 1 if @current_page == 0
|
||||||
|
|
||||||
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
|
|
||||||
|
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
|
site_dataset = site_dataset.paginate @current_page, Site::BROWSE_PAGINATION_LENGTH
|
||||||
@page_count = site_dataset.page_count || 1
|
@page_count = site_dataset.page_count || 1
|
||||||
@sites = site_dataset.all
|
@sites = site_dataset.all
|
||||||
|
@ -10,11 +20,14 @@ get '/browse/?' do
|
||||||
erb :browse
|
erb :browse
|
||||||
end
|
end
|
||||||
|
|
||||||
def browse_sites_dataset
|
def education_sites_dataset
|
||||||
@current_page = params[:current_page]
|
site_dataset = Site.filter is_deleted: false
|
||||||
@current_page = @current_page.to_i
|
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
||||||
@current_page = 1 if @current_page == 0
|
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
|
||||||
|
@ -30,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)
|
||||||
|
@ -53,15 +79,17 @@ 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] && params[:sort_by] != 'followers'
|
||||||
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
||||||
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)]
|
||||||
|
|
|
@ -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
|
||||||
|
@ -58,7 +61,8 @@ post '/create' do
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,4 +89,4 @@ post '/create' do
|
||||||
|
|
||||||
session[:id] = @site.id
|
session[:id] = @site.id
|
||||||
{result: 'ok'}.to_json
|
{result: 'ok'}.to_json
|
||||||
end
|
end
|
||||||
|
|
13
app/index.rb
13
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
|
||||||
|
@ -71,3 +78,7 @@ get '/legal/?' do
|
||||||
@title = 'Legal Guide to Neocities'
|
@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:
|
4
app/search.rb
Normal file
4
app/search.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
get '/search' do
|
||||||
|
erb :'search'
|
||||||
|
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
|
||||||
|
|
||||||
current_site.install_new_html_file name
|
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
|
||||||
|
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
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
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
|
||||||
|
|
|
@ -26,8 +26,7 @@ post '/webhooks/stripe' 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',
|
||||||
|
@ -38,8 +37,7 @@ post '/webhooks/stripe' 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
|
||||||
|
@ -54,3 +52,23 @@ post '/webhooks/stripe' do
|
||||||
|
|
||||||
'ok'
|
'ok'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
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
|
||||||
|
@ -15,6 +25,10 @@ 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
|
if session[:banned] || current_site.is_banned || parent_site.is_banned
|
||||||
|
@ -39,10 +53,11 @@ def parent_site
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_unbanned_ip
|
def require_unbanned_ip
|
||||||
if session[:banned] || Site.banned_ip?(request.ip)
|
if session[:banned] || (is_banned_ip = Site.banned_ip?(request.ip))
|
||||||
signout
|
signout
|
||||||
session[:banned] = true
|
session[:banned] = request.ip if !session[:banned]
|
||||||
flash[:error] = 'Site creation has been banned due to ToS violation/spam. '+
|
|
||||||
|
flash[:error] = 'Site creation has been banned due to a Terms of Service violation from your location. '+
|
||||||
'If you believe this to be in error, <a href="/contact">contact the site admin</a>.'
|
'If you believe this to be in error, <a href="/contact">contact the site admin</a>.'
|
||||||
return {result: 'error'}.to_json
|
return {result: 'error'}.to_json
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,3 +10,4 @@ email_unsubscribe_token: "somethingrandomderrrrp"
|
||||||
paypal_api_username: derp
|
paypal_api_username: derp
|
||||||
paypal_api_password: ing
|
paypal_api_password: ing
|
||||||
paypal_api_signature: tonz
|
paypal_api_signature: tonz
|
||||||
|
logs_path: "/tmp/neocitiestestlogs"
|
||||||
|
|
|
@ -75,18 +75,6 @@ if ENV['RACK_ENV'] == 'development'
|
||||||
end
|
end
|
||||||
# :nocov:
|
# :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
|
|
||||||
# :nocov:
|
|
||||||
|
|
||||||
Sequel.datetime_class = Time
|
Sequel.datetime_class = Time
|
||||||
Sequel.extension :core_extensions
|
Sequel.extension :core_extensions
|
||||||
Sequel.extension :migration
|
Sequel.extension :migration
|
||||||
|
@ -138,3 +126,11 @@ PayPal::Recurring.configure do |config|
|
||||||
config.password = $config['paypal_api_password']
|
config.password = $config['paypal_api_password']
|
||||||
config.signature = $config['paypal_api_signature']
|
config.signature = $config['paypal_api_signature']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'csv'
|
||||||
|
|
||||||
|
$country_codes = {}
|
||||||
|
|
||||||
|
CSV.foreach("./files/country_codes.csv") do |row|
|
||||||
|
$country_codes[row.last] = row.first
|
||||||
|
end
|
||||||
|
|
|
@ -10,15 +10,24 @@ 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).to_comma_separated} GB"
|
'MB' => 1000 * 1000 * 1000,
|
||||||
else
|
'GB' => 1000 * 1000 * 1000 * 1000,
|
||||||
"#{space.to_comma_separated} MB"
|
'TB' => 1000 * 1000 * 1000 * 1000 * 1000
|
||||||
end
|
}.each_pair { |e, s|
|
||||||
|
if self < s
|
||||||
|
computed = (self.to_f / (s / 1000)).round(2)
|
||||||
|
unit = e
|
||||||
|
break
|
||||||
|
end
|
||||||
|
}
|
||||||
|
computed = computed.to_i if computed.modulo(1) == 0.0
|
||||||
|
|
||||||
|
"#{computed} #{unit}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_comma_separated
|
def to_comma_separated
|
||||||
|
|
|
@ -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
|
||||||
|
|
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
|
361
models/site.rb
361
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
|
||||||
|
@ -360,25 +394,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 +574,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?
|
# We gotta flush the dirname too if it's an index file.
|
||||||
PurgeCacheWorker.perform_async payload
|
if relative_path != '' && relative_path.match(/\/$|index\.html?$/i)
|
||||||
|
PurgeCacheOrderWorker.perform_async username, relative_path
|
||||||
|
PurgeCacheOrderWorker.perform_async username, Pathname(relative_path).dirname.to_s
|
||||||
|
else
|
||||||
|
PurgeCacheOrderWorker.perform_async username, relative_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_file(path, uploaded, opts={})
|
Rye::Cmd.add_command :ipfs, nil, 'add', :r
|
||||||
relative_path = scrubbed_path path
|
|
||||||
path = files_path path
|
|
||||||
pathname = Pathname(path)
|
|
||||||
|
|
||||||
site_file = site_files_dataset.where(path: relative_path).first
|
def add_to_ipfs
|
||||||
|
# Not ideal. An SoA version is in progress.
|
||||||
uploaded_sha1 = Digest::SHA1.file(uploaded.path).hexdigest
|
if $config['ipfs_ssh_host'] && $config['ipfs_ssh_user']
|
||||||
|
rbox = Rye::Box.new $config['ipfs_ssh_host'], :user => $config['ipfs_ssh_user']
|
||||||
if site_file && site_file.sha1_hash == uploaded_sha1
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if relative_path == 'index.html' && opts[:new_install] != true
|
|
||||||
begin
|
begin
|
||||||
new_title = Nokogiri::HTML(File.read(uploaded.path)).css('title').first.text
|
response = rbox.ipfs "sites/#{self.username.gsub(/\/|\.\./, '')}"
|
||||||
rescue NoMethodError => e
|
output_array = response
|
||||||
else
|
ensure
|
||||||
if new_title.length < TITLE_MAX
|
rbox.disconnect
|
||||||
self.title = new_title
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
self.site_changed = true
|
line = Cocaine::CommandLine.new('ipfs', 'add -r :path')
|
||||||
self.site_updated_at = Time.now
|
response = line.run path: files_path
|
||||||
self.updated_at = Time.now
|
output_array = response.to_s.split("\n")
|
||||||
|
|
||||||
save_changes(validate: false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if pathname.extname.match HTML_REGEX
|
output_array.last.split(' ')[1]
|
||||||
# SPAM and phishing checking code goes here
|
end
|
||||||
|
|
||||||
|
def archive!
|
||||||
|
#if ENV["RACK_ENV"] == 'test'
|
||||||
|
# ipfs_hash = "QmcKi2ae3uGb1kBg1yBpsuwoVqfmcByNdMiZ2pukxyLWD8"
|
||||||
|
#else
|
||||||
|
#end
|
||||||
|
|
||||||
|
ipfs_hash = add_to_ipfs
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
dirname = pathname.dirname.to_s
|
def latest_archive
|
||||||
|
@latest_archive ||= archives_dataset.order(:updated_at.desc).first
|
||||||
if !File.exists? dirname
|
|
||||||
FileUtils.mkdir_p dirname
|
|
||||||
end
|
|
||||||
|
|
||||||
uploaded_size = uploaded.size
|
|
||||||
|
|
||||||
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 +641,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 +664,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 +816,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 +854,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!
|
||||||
|
@ -945,6 +943,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 +965,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 +1089,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,6 +1,5 @@
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
243
models/stat.rb
243
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
|
||||||
end
|
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
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
background:#f6f0e6;
|
background:#f6f0e6;
|
||||||
min-height:500px;
|
min-height:500px;
|
||||||
padding-bottom:50px;
|
padding-bottom:50px;
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6{
|
h1, h2, h3, h4, h5, h6{
|
||||||
color:#e93250
|
color:#e93250
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.content, .footer-Content {
|
.content, .footer-Content {
|
||||||
padding: 20px 3%;
|
padding: 20px 3%;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
padding: 20px 7%;
|
padding: 20px 7%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width:1300px){
|
@media screen and (min-width:1300px){
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
.interior .header-Outro {
|
.interior .header-Outro {
|
||||||
padding-top: 30px;
|
padding-top: 30px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
|
@ -47,13 +47,13 @@
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
float: right;
|
float: right;
|
||||||
width: 35%;
|
width: 35%;
|
||||||
|
|
||||||
.col-50 {
|
.col-50 {
|
||||||
width:100%;
|
width:100%;
|
||||||
margin-bottom: 1em!important;
|
margin-bottom: 1em!important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.site-url {
|
.site-url {
|
||||||
font-size:18px;
|
font-size:18px;
|
||||||
|
@ -78,11 +78,11 @@
|
||||||
-webkit-box-shadow: 0px 1px 1px 1px rgba(0,0,0,0.10);
|
-webkit-box-shadow: 0px 1px 1px 1px rgba(0,0,0,0.10);
|
||||||
box-shadow: 0px 1px 1px 1px rgba(0,0,0,0.10);
|
box-shadow: 0px 1px 1px 1px rgba(0,0,0,0.10);
|
||||||
padding: 25px 3% 40px 3%;
|
padding: 25px 3% 40px 3%;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
padding: 22px 7% 40px 7%;
|
padding: 22px 7% 40px 7%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -122,19 +122,19 @@
|
||||||
}
|
}
|
||||||
.site-url {
|
.site-url {
|
||||||
margin-top: -9px;
|
margin-top: -9px;
|
||||||
}
|
}
|
||||||
.btn-Action {
|
.btn-Action {
|
||||||
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) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
margin: 6px 0px 8px;
|
margin: 6px 0px 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.row.content {
|
.row.content {
|
||||||
margin-left: 6%;
|
margin-left: 6%;
|
||||||
margin-right: 6%;
|
margin-right: 6%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.interior .header-Outro a {color:#E93250}
|
.interior .header-Outro a {color:#E93250}
|
||||||
|
@ -151,7 +151,7 @@
|
||||||
-webkit-box-shadow: 1px 2px 5px 2px rgba(0,0,0,0.10);
|
-webkit-box-shadow: 1px 2px 5px 2px rgba(0,0,0,0.10);
|
||||||
box-shadow: 1px 2px 5px 2px rgba(0,0,0,0.10);
|
box-shadow: 1px 2px 5px 2px rgba(0,0,0,0.10);
|
||||||
border: 4px solid white;
|
border: 4px solid white;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@
|
||||||
-webkit-background-size:cover;
|
-webkit-background-size:cover;
|
||||||
background-size:cover;
|
background-size:cover;
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
|
@ -203,7 +203,7 @@
|
||||||
}
|
}
|
||||||
.interior .header-Outro.dashboard .col-50:nth-of-type(2) {
|
.interior .header-Outro.dashboard .col-50:nth-of-type(2) {
|
||||||
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: 22px;
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
|
@ -215,7 +215,7 @@
|
||||||
background-size: 77px 81px;
|
background-size: 77px 81px;
|
||||||
padding: 20px 20px 20px 111px;
|
padding: 20px 20px 20px 111px;
|
||||||
margin-bottom: 13px;
|
margin-bottom: 13px;
|
||||||
|
|
||||||
@media (max-device-width: 480px), screen and (max-width: 800px) {
|
@media (max-device-width: 480px), screen and (max-width: 800px) {
|
||||||
padding: 111px 20px 20px 20px;
|
padding: 111px 20px 20px 20px;
|
||||||
background-position: center 20px;
|
background-position: center 20px;
|
||||||
|
@ -233,7 +233,7 @@
|
||||||
}
|
}
|
||||||
.files {
|
.files {
|
||||||
float:left;
|
float:left;
|
||||||
background: #E4D8CB;
|
background: #EAE1D5;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 7px;
|
margin-top: 7px;
|
||||||
|
@ -262,7 +262,7 @@
|
||||||
}
|
}
|
||||||
.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 8px 4px 0;
|
||||||
}
|
}
|
||||||
|
@ -278,12 +278,12 @@
|
||||||
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 {
|
||||||
background: url(/img/drag-drop.png) no-repeat center center;
|
background: url(/img/drag-drop.png) no-repeat center center;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
background: 0;
|
background: 0;
|
||||||
}
|
}
|
||||||
|
@ -312,7 +312,7 @@
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
.files .progress-bar {
|
.files .progress-bar {
|
||||||
background:#CCCCCC;
|
background:#CCCCCC;
|
||||||
-webkit-border-radius: 8px;
|
-webkit-border-radius: 8px;
|
||||||
-moz-border-radius: 8px;
|
-moz-border-radius: 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
@ -472,7 +472,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
clear: both;
|
clear: both;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
h2:first-of-type, h3:first-of-type {
|
h2:first-of-type, h3:first-of-type {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -516,7 +516,7 @@
|
||||||
width: 66.95%;
|
width: 66.95%;
|
||||||
left: 33.05%;
|
left: 33.05%;
|
||||||
background: white;
|
background: white;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
|
@ -526,14 +526,14 @@
|
||||||
.site-profile .content.misc-page.columns .col-66 {
|
.site-profile .content.misc-page.columns .col-66 {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: 38em;
|
min-height: 38em;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
min-height: 0px;
|
min-height: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.news-feed .content.misc-page.columns .col-66 {
|
.news-feed .content.misc-page.columns .col-66 {
|
||||||
min-height: 56em;
|
min-height: 56em;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
@ -542,14 +542,14 @@
|
||||||
width: 33%;
|
width: 33%;
|
||||||
left: 33%;
|
left: 33%;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.interior .header-Outro.with-columns {
|
.interior .header-Outro.with-columns {
|
||||||
padding-top: 22px;
|
padding-top: 22px;
|
||||||
}
|
}
|
||||||
.interior .header-Outro.with-columns h3 {
|
.interior .header-Outro.with-columns h3 {
|
||||||
float: left;
|
float: left;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -560,14 +560,14 @@
|
||||||
}
|
}
|
||||||
.interior .header-Outro.with-columns .col {
|
.interior .header-Outro.with-columns .col {
|
||||||
padding: 25px 0 8px 30px;
|
padding: 25px 0 8px 30px;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
padding: 10px 0 0 27px;
|
padding: 10px 0 0 27px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.interior .header-Outro.with-columns .col-32 {
|
.interior .header-Outro.with-columns .col-32 {
|
||||||
width: 33%;
|
width: 33%;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -583,10 +583,10 @@
|
||||||
.interior .header-Outro.with-columns .col-66 {
|
.interior .header-Outro.with-columns .col-66 {
|
||||||
width: 67%;
|
width: 67%;
|
||||||
border-right: 1px solid #0B0F11;
|
border-right: 1px solid #0B0F11;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
padding: 15px 0 27px 30px;
|
padding: 15px 0 27px 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.interior .header-Outro.with-columns .col-32 .edit {
|
.interior .header-Outro.with-columns .col-32 .edit {
|
||||||
|
@ -600,7 +600,7 @@
|
||||||
margin-top: 1.4em;
|
margin-top: 1.4em;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
margin-left: 1.5em;
|
margin-left: 1.5em;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
clear: left;
|
clear: left;
|
||||||
|
@ -635,7 +635,7 @@
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
float: left;
|
float: left;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
@ -661,11 +661,11 @@
|
||||||
clear: both;
|
clear: both;
|
||||||
margin: 20px auto 2em auto;
|
margin: 20px auto 2em auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.btn-Action:last-child {
|
.btn-Action:last-child {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -750,7 +750,7 @@ a.tag:hover {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
.news-item .icon-mini {
|
.news-item .icon-mini {
|
||||||
|
|
||||||
}
|
}
|
||||||
.news-item.update .icon {
|
.news-item.update .icon {
|
||||||
background: #E93250;
|
background: #E93250;
|
||||||
|
@ -797,7 +797,7 @@ a.tag:hover {
|
||||||
float: left;
|
float: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.news-item .file a:hover {
|
.news-item .file a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.news-item .html-thumbnail {
|
.news-item .html-thumbnail {
|
||||||
|
@ -863,7 +863,7 @@ a.tag:hover {
|
||||||
.signup-Area.large {
|
.signup-Area.large {
|
||||||
width: 418px;
|
width: 418px;
|
||||||
height: 236px;
|
height: 236px;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
@ -894,14 +894,14 @@ a.tag:hover {
|
||||||
a:first-of-type {
|
a:first-of-type {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.interior .header-Outro .stats {
|
.interior .header-Outro .stats {
|
||||||
margin-bottom: 1.3em;
|
margin-bottom: 1.3em;
|
||||||
float: left;
|
float: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 1.9em;
|
margin-top: 1.9em;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
float: none;
|
float: none;
|
||||||
width: 270px;
|
width: 270px;
|
||||||
|
@ -925,7 +925,7 @@ a.tag:hover {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-right: 28px;
|
margin-right: 28px;
|
||||||
color: #84997E;
|
color: #84997E;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
|
@ -941,21 +941,21 @@ a.tag:hover {
|
||||||
.following {
|
.following {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.unfollow {
|
.unfollow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#followLink.is-following {
|
#followLink.is-following {
|
||||||
|
|
||||||
|
|
||||||
.follow {
|
.follow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.following {
|
.following {
|
||||||
display: block;
|
display: block;
|
||||||
width: 5.9em;
|
width: 5.9em;
|
||||||
}
|
}
|
||||||
.unfollow {
|
.unfollow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -963,10 +963,10 @@ a.tag:hover {
|
||||||
.follow {
|
.follow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.following {
|
.following {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.unfollow {
|
.unfollow {
|
||||||
display: block;
|
display: block;
|
||||||
width: 5.9em;
|
width: 5.9em;
|
||||||
}
|
}
|
||||||
|
@ -997,7 +997,7 @@ a.tag:hover {
|
||||||
border: 3px solid white;
|
border: 3px solid white;
|
||||||
-webkit-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.3);
|
-webkit-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.3);
|
||||||
-moz-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.3);
|
-moz-box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.3);
|
||||||
box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.3);
|
box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.3);
|
||||||
width: 72px;
|
width: 72px;
|
||||||
}
|
}
|
||||||
.archives img {
|
.archives img {
|
||||||
|
@ -1014,11 +1014,11 @@ a.tag:hover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px 0 15px 30px;
|
padding: 20px 0 15px 30px;
|
||||||
margin: 0 0 3px 0;
|
margin: 0 0 3px 0;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 86%;
|
width: 86%;
|
||||||
float: left;
|
float: left;
|
||||||
|
|
||||||
@media (max-width:950px) {
|
@media (max-width:950px) {
|
||||||
width: 82%;
|
width: 82%;
|
||||||
}
|
}
|
||||||
|
@ -1031,7 +1031,7 @@ a.tag:hover {
|
||||||
.comment-policy {
|
.comment-policy {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
margin-right: 45px;
|
margin-right: 45px;
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
|
@ -1096,11 +1096,11 @@ a.tag:hover {
|
||||||
.row {
|
.row {
|
||||||
margin: 5.5em 10%;
|
margin: 5.5em 10%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px){
|
@media (max-device-width:480px), screen and (max-width:800px){
|
||||||
margin: 4em 5%;
|
margin: 4em 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
margin-top: 3.5em;
|
margin-top: 3.5em;
|
||||||
}
|
}
|
||||||
|
@ -1114,7 +1114,7 @@ a.tag:hover {
|
||||||
padding-top: 2em;
|
padding-top: 2em;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px){
|
@media (max-device-width:480px), screen and (max-width:800px){
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
|
@ -1129,7 +1129,7 @@ a.tag:hover {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border: 13px solid #4f7d88;
|
border: 13px solid #4f7d88;
|
||||||
@include box-shadow(1px 1px 2px 0px rgba(0, 0, 0, 0.18));
|
@include box-shadow(1px 1px 2px 0px rgba(0, 0, 0, 0.18));
|
||||||
|
|
||||||
}
|
}
|
||||||
&:nth-child(3) .screenshot {
|
&:nth-child(3) .screenshot {
|
||||||
border: 13px solid $c-Brand-1;
|
border: 13px solid $c-Brand-1;
|
||||||
|
@ -1160,7 +1160,7 @@ a.tag:hover {
|
||||||
border-bottom: 1px solid #801629;
|
border-bottom: 1px solid #801629;
|
||||||
padding: 30px 0;
|
padding: 30px 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
h2.delta {
|
h2.delta {
|
||||||
color: white;
|
color: white;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -1193,17 +1193,45 @@ 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;
|
||||||
|
|
||||||
.row:first-of-type {
|
.row:first-of-type {
|
||||||
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;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px){
|
@media (max-device-width:480px), screen and (max-width:800px){
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -1237,7 +1265,7 @@ a.tag:hover {
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px){
|
@media (max-device-width:480px), screen and (max-width:800px){
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -1247,10 +1275,10 @@ a.tag:hover {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
|
|
||||||
@media (max-width:1170px) and (min-width:900px){
|
@media (max-width:1170px) and (min-width:900px){
|
||||||
margin-right: 25px!important;
|
margin-right: 25px!important;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0!important;
|
margin-right: 0!important;
|
||||||
}
|
}
|
||||||
|
@ -1264,11 +1292,11 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
.section .logo.wired {
|
.section .logo.wired {
|
||||||
width: 211px;
|
width: 211px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
background-image: url(/img/wired-logo.png);
|
background-image: url(/img/wired-logo.png);
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
margin-right: 38px;
|
margin-right: 38px;
|
||||||
|
|
||||||
@media (max-width:1170px) and (min-width:900px){
|
@media (max-width:1170px) and (min-width:900px){
|
||||||
width: 170px;
|
width: 170px;
|
||||||
}
|
}
|
||||||
|
@ -1278,7 +1306,7 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
.section .logo.fastco {
|
.section .logo.fastco {
|
||||||
width: 262px;
|
width: 262px;
|
||||||
height: 39px;
|
height: 39px;
|
||||||
background-image: url(/img/fastcompany-logo.png);
|
background-image: url(/img/fastcompany-logo.png);
|
||||||
margin-top: 7px;
|
margin-top: 7px;
|
||||||
@media (max-width:1170px) and (min-width:900px){
|
@media (max-width:1170px) and (min-width:900px){
|
||||||
|
@ -1287,7 +1315,7 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
.section .logo.vice {
|
.section .logo.vice {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
background-image: url(/img/vice-logo.png);
|
background-image: url(/img/vice-logo.png);
|
||||||
@media (max-width:1170px) and (min-width:900px){
|
@media (max-width:1170px) and (min-width:900px){
|
||||||
width: 120px;
|
width: 120px;
|
||||||
|
@ -1295,7 +1323,7 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.section .logo.ars {
|
.section .logo.ars {
|
||||||
width: 187px;
|
width: 187px;
|
||||||
height: 62px;
|
height: 62px;
|
||||||
background-image: url(/img/ars-logo.png);
|
background-image: url(/img/ars-logo.png);
|
||||||
margin-top: -4px;
|
margin-top: -4px;
|
||||||
|
@ -1313,7 +1341,7 @@ a.tag:hover {
|
||||||
.section .quote {
|
.section .quote {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
margin: 0 auto .5em auto;
|
margin: 0 auto .5em auto;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -1323,9 +1351,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) {
|
||||||
|
@ -1470,12 +1498,12 @@ a.tag:hover {
|
||||||
border: 0;
|
border: 0;
|
||||||
background: 0;
|
background: 0;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
border-bottom: 1px dotted #bbb;
|
border-bottom: 1px dotted #bbb;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-device-width:480px), screen and (max-width:800px) {
|
@media(max-device-width:480px), screen and (max-width:800px) {
|
||||||
width: 26%;
|
width: 26%;
|
||||||
}
|
}
|
||||||
|
@ -1493,7 +1521,7 @@ a.tag:hover {
|
||||||
}
|
}
|
||||||
.section.plans.welcome {
|
.section.plans.welcome {
|
||||||
padding: 63px 3% 0 3%;
|
padding: 63px 3% 0 3%;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #5e95a1;
|
color: #5e95a1;
|
||||||
}
|
}
|
||||||
|
@ -1501,7 +1529,7 @@ a.tag:hover {
|
||||||
width: 68%;
|
width: 68%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
@media(max-device-width:480px), screen and (max-width:550px) {
|
@media(max-device-width:480px), screen and (max-width:550px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1511,18 +1539,18 @@ a.tag:hover {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
max-width: 990px;
|
max-width: 990px;
|
||||||
min-width: 900px;
|
min-width: 900px;
|
||||||
|
|
||||||
@media(max-device-width:480px), screen and (max-width:550px) {
|
@media(max-device-width:480px), screen and (max-width:550px) {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>.row >.col {
|
>.row >.col {
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-device-width:480px), screen and (max-width:550px) {
|
@media(max-device-width:480px), screen and (max-width:550px) {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -1540,10 +1568,10 @@ a.tag:hover {
|
||||||
float: left;
|
float: left;
|
||||||
background: 0;
|
background: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
clear: both;
|
clear: both;
|
||||||
|
|
||||||
&.main-features {
|
&.main-features {
|
||||||
font-size: 140%;
|
font-size: 140%;
|
||||||
margin-bottom: .7em;
|
margin-bottom: .7em;
|
||||||
|
@ -1579,7 +1607,7 @@ a.tag:hover {
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
|
|
||||||
.main-features {
|
.main-features {
|
||||||
margin-bottom: 76px;
|
margin-bottom: 76px;
|
||||||
}
|
}
|
||||||
|
@ -1590,7 +1618,7 @@ a.tag:hover {
|
||||||
border: 1px solid #E0E0E0;
|
border: 1px solid #E0E0E0;
|
||||||
@include box-shadow(-1px 1px 5px 0px rgba(0,0,0,0.1));
|
@include box-shadow(-1px 1px 5px 0px rgba(0,0,0,0.1));
|
||||||
margin-bottom: 1.2em!important;
|
margin-bottom: 1.2em!important;
|
||||||
|
|
||||||
.col:first-child {
|
.col:first-child {
|
||||||
width: 38%;
|
width: 38%;
|
||||||
}
|
}
|
||||||
|
@ -1614,7 +1642,7 @@ a.tag:hover {
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media(max-device-width:480px), screen and (max-width:550px) {
|
@media(max-device-width:480px), screen and (max-width:550px) {
|
||||||
.col.free, .col.supporter, .col.supporter .col {
|
.col.free, .col.supporter, .col.supporter .col {
|
||||||
width: 100%!important;
|
width: 100%!important;
|
||||||
}
|
}
|
||||||
|
@ -1633,11 +1661,11 @@ a.tag:hover {
|
||||||
.section.bottom-signup {
|
.section.bottom-signup {
|
||||||
// padding-top: 1em;
|
// padding-top: 1em;
|
||||||
// padding-bottom: 6.5em;
|
// padding-bottom: 6.5em;
|
||||||
|
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
background: #4F7E89;
|
background: #4F7E89;
|
||||||
padding-bottom: 7em;
|
padding-bottom: 7em;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
@ -1743,7 +1771,7 @@ a.tag:hover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 22px;
|
margin-top: 22px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|
||||||
a, a:visited {
|
a, a:visited {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
@ -1762,17 +1790,17 @@ a.tag:hover {
|
||||||
.tools {
|
.tools {
|
||||||
color: #8099A7;
|
color: #8099A7;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
.theme {
|
.theme {
|
||||||
font-size: .9em;
|
font-size: .9em;
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
||||||
select#theme {
|
select#theme {
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
background: #25333c;
|
background: #25333c;
|
||||||
color: #8099A7;
|
color: #8099A7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -1780,7 +1808,7 @@ a.tag:hover {
|
||||||
#saveButton {
|
#saveButton {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.tooltip {
|
.tooltip {
|
||||||
&.bottom .tooltip-arrow {
|
&.bottom .tooltip-arrow {
|
||||||
border-bottom-color: #971D31;
|
border-bottom-color: #971D31;
|
||||||
}
|
}
|
||||||
|
@ -1820,7 +1848,7 @@ a.tag:hover {
|
||||||
background: rgba(228, 228, 228, 0.42);
|
background: rgba(228, 228, 228, 0.42);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 26px 0 5px;
|
padding: 26px 0 5px;
|
||||||
p {
|
p {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
@ -1852,4 +1880,75 @@ a.tag:hover {
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
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%;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: .2em;
|
||||||
|
a {
|
||||||
|
color: #2c3e50!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -51,4 +51,4 @@ describe '/admin' do
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -4,4 +4,6 @@ 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,6 +30,7 @@ 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
|
||||||
|
@ -48,19 +49,15 @@ 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 to create for existing site' do
|
||||||
|
@existing_site = Fabricate :site
|
||||||
fill_in_valid
|
fill_in_valid
|
||||||
click_signup_button
|
fill_in 'username', with: @existing_site.username
|
||||||
page.must_have_content 'Welcome to Neocities'
|
|
||||||
Capybara.reset_sessions!
|
|
||||||
visit_signup
|
|
||||||
sleep 0.3
|
|
||||||
fill_in 'username', with: @site[:username]
|
|
||||||
fill_in 'password', with: @site[:password]
|
|
||||||
click_signup_button
|
click_signup_button
|
||||||
page.must_have_content 'already taken'
|
page.must_have_content 'already taken'
|
||||||
end
|
end
|
||||||
|
@ -113,9 +110,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: ''
|
||||||
|
@ -139,7 +133,7 @@ puts "$$$$$$$$$$$$$$$$$$$$$$ TODO FIX TAGS TESTS"
|
||||||
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails with invalid tag chars' do
|
it 'fails with invalid tag chars' do
|
||||||
fill_in_valid
|
fill_in_valid
|
||||||
fill_in 'new_tags_string', with: '$POLICE OFFICER$$$$$, derp'
|
fill_in 'new_tags_string', with: '$POLICE OFFICER$$$$$, derp'
|
||||||
click_signup_button
|
click_signup_button
|
||||||
|
@ -179,9 +173,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
|
||||||
|
|
|
@ -50,4 +50,4 @@ I18n.enforce_available_locales = true
|
||||||
|
|
||||||
Mail.defaults do
|
Mail.defaults do
|
||||||
delivery_method :test
|
delivery_method :test
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -77,4 +79,4 @@ describe Site do
|
||||||
site.suggestions.length.must_equal Site::SUGGESTIONS_LIMIT
|
site.suggestions.length.must_equal Site::SUGGESTIONS_LIMIT
|
||||||
end
|
end
|
||||||
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>
|
||||||
<li><a href="/blog">Blog</a></li>
|
<% unless is_education? %>
|
||||||
<li><a href="/api">API</a></li>
|
<li><a href="/blog">Blog</a></li>
|
||||||
<li><a href="/press">Press</a></li>
|
<li><a href="/api">API</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,17 +30,19 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
|
<% unless is_education? %>
|
||||||
<li>
|
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
|
||||||
<a href="/?activity=mine"><span class="float-Left">Activity</span>
|
<li>
|
||||||
<% if current_site.unseen_notifications_count > 0 %>
|
<a href="/?activity=mine"><span class="float-Left">Activity</span>
|
||||||
<span class="notification-value"><%= current_site.unseen_notifications_count %></span>
|
<% if current_site.unseen_notifications_count > 0 %>
|
||||||
<% end %>
|
<span class="notification-value"><%= current_site.unseen_notifications_count %></span>
|
||||||
</a>
|
<% end %>
|
||||||
</li>
|
</a>
|
||||||
<li class="divider"></li>
|
</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,19 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/browse">Websites</a>
|
<a href="/browse">Websites</a>
|
||||||
</li>
|
</li>
|
||||||
|
<% unless is_education? %>
|
||||||
|
<li>
|
||||||
|
<a href="/search">Search</a>
|
||||||
|
</li>
|
||||||
<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>
|
|
@ -21,31 +21,39 @@
|
||||||
<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">
|
||||||
<label class="text-Label" for="sort_by">Sort by:</label>
|
<% unless is_education? %>
|
||||||
<div class="select-Container">
|
<label class="text-Label" for="sort_by">Sort by:</label>
|
||||||
<select name="sort_by" id="sort_by" class="input-Select">
|
<div class="select-Container">
|
||||||
<option value="last_updated" <%= params[:sort_by] == 'last_updated' ? 'selected' : '' %>>Last Updated</option>
|
<select name="sort_by" id="sort_by" class="input-Select">
|
||||||
<option value="views" <%= params[:sort_by] == 'views' ? 'selected' : '' %>>Most Views</option>
|
<option value="followers" <%= params[:sort_by] == 'followers' ? 'selected' : '' %>>Most Followed</option>
|
||||||
<option value="hits" <%= params[:sort_by] == 'hits' ? 'selected' : '' %>>Most Hits</option>
|
<option value="last_updated" <%= params[:sort_by] == 'last_updated' ? 'selected' : '' %>>Last Updated</option>
|
||||||
<option value="newest" <%= params[:sort_by] == 'newest' ? 'selected' : '' %>>Newest</option>
|
<option value="supporters" <%= params[:sort_by] == 'supporters' ? 'selected' : '' %>>Neocities Supporters</option>
|
||||||
<option value="oldest" <%= params[:sort_by] == 'oldest' ? 'selected' : '' %>>Oldest</option>
|
<option value="featured" <%= params[:sort_by] == 'featured' ? 'selected' : '' %>>Featured</option>
|
||||||
<option value="random" <%= params[:sort_by] == 'random' ? 'selected' : '' %>>Random</option>
|
<option value="views" <%= params[:sort_by] == 'views' ? 'selected' : '' %>>Most Views</option>
|
||||||
</select>
|
<option value="hits" <%= params[:sort_by] == 'hits' ? 'selected' : '' %>>Most Hits</option>
|
||||||
</div>
|
<option value="newest" <%= params[:sort_by] == 'newest' ? 'selected' : '' %>>Newest</option>
|
||||||
<!--
|
<option value="oldest" <%= params[:sort_by] == 'oldest' ? 'selected' : '' %>>Oldest</option>
|
||||||
<div>
|
<option value="random" <%= params[:sort_by] == 'random' ? 'selected' : '' %>>Random</option>
|
||||||
<input name="is_nsfw" type="checkbox" value="true" <%= params[:is_nsfw].to_s == 'true' ? 'checked' : '' %>> Show 18+ content
|
</select>
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
|
<!--
|
||||||
|
<div>
|
||||||
|
<input name="is_nsfw" type="checkbox" value="true" <%= params[:is_nsfw].to_s == 'true' ? 'checked' : '' %>> Show 18+ content
|
||||||
|
</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' %>
|
||||||
<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">
|
<label class="text-Label" for="tag"><span class="hide-on-mobile">Search by </span>Tag:</label>
|
||||||
<input style="vertical-align: -4px;margin-left: 4px;" type="submit" class="btn-Action" value="Search">
|
<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">
|
||||||
|
<% end %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,12 +149,14 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% unless is_education? %>
|
||||||
<div class="row website-Gallery content int-Gall tags">
|
<div class="row website-Gallery content int-Gall tags">
|
||||||
<h3>Popular Tags</h3>
|
<h3>Popular Tags</h3>
|
||||||
<p>
|
<p>
|
||||||
<% Tag.popular_names(100).each do |tag| %>
|
<% Tag.popular_names(100).each do |tag| %>
|
||||||
<a href="/browse?tag=<%= Rack::Utils.escape tag[:name] %>"><%= tag[:name] %></a>
|
<a href="/browse?tag=<%= Rack::Utils.escape tag[:name] %>"><%= tag[:name] %></a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -102,7 +101,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>
|
||||||
|
@ -189,8 +188,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 +204,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 +285,12 @@
|
||||||
|
|
||||||
this.on("error", function(file, errorMessage) {
|
this.on("error", function(file, errorMessage) {
|
||||||
hideUploadProgress()
|
hideUploadProgress()
|
||||||
location.href = '/dashboard<%= @dir ? "?dir=#{@dir}" : "" %>'
|
// Guess a directory upload error
|
||||||
// alert('Failed: '+errorMessage)
|
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}" : "" %>'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
||||||
|
@ -258,24 +301,18 @@
|
||||||
$('#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+'">')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="modal hide fade" id="createDir" tabindex="-1" role="dialog" aria-labelledby="createDirLabel" aria-hidden="true">
|
$('#createDir').on('shown', function () {
|
||||||
<form method="post" action="/site/create_directory">
|
$('#newDirInput').focus();
|
||||||
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
|
})
|
||||||
<input type="hidden" value="<%= @dir %>" name="dir">
|
|
||||||
<div class="modal-header">
|
$('#createFile').on('shown', function () {
|
||||||
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
|
$('#newFileInput').focus();
|
||||||
<h3 id="createDirLabel">Create Folder</h3>
|
})
|
||||||
</div>
|
</script>
|
||||||
<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>
|
||||||
|
|
569
views/index.erb
569
views/index.erb
|
@ -1,203 +1,175 @@
|
||||||
<!doctype html>
|
<body class="hp">
|
||||||
<!--[if IE 8 ]><html lang="en" class="ieAll ie8"><![endif]-->
|
<a id="new"></a>
|
||||||
<!--[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>
|
<div class="page">
|
||||||
<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" />
|
<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>
|
||||||
|
|
||||||
<meta property="og:title" content="Neocities"/>
|
<ul class="status-Nav">
|
||||||
<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" />
|
<% if !signed_in? %>
|
||||||
<link rel="apple-touch-icon-precomposed" href="#apple-icon-144.png" />
|
<li>
|
||||||
<link rel="apple-touch-startup-image" href="#startup.png" />
|
<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 %>
|
||||||
<!-- Mobile Meta -->
|
<div class="alert alert-block txt-Center">
|
||||||
<meta name="HandheldFriendly" content="True" />
|
<% flash.keys.each do |key| %>
|
||||||
<meta name="MobileOptimized" content="320" />
|
<%== flash[key] %>
|
||||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
|
<% end %>
|
||||||
|
</div>
|
||||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
|
<% end %>
|
||||||
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script type="text/javascript" src="/js/html5.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
<script src="/js/jquery-1.11.0.min.js"></script>
|
|
||||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="hp">
|
|
||||||
<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 txt-Center">
|
|
||||||
<p style="padding:5px">
|
|
||||||
<% flash.keys.each do |key| %>
|
|
||||||
<%== flash[key] %>
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="int-Logo hp-Logo">
|
<div class="int-Logo hp-Logo">
|
||||||
<a href="/" title="back to home">
|
<a href="/" title="back to home">
|
||||||
<span class="hidden">Neocities.org</span>
|
<span class="hidden">Neocities.org</span>
|
||||||
<img src="/img/cat.png" alt="Neocities.org" />
|
<img src="/img/cat.png" alt="Neocities.org" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="header-Intro">
|
<section class="header-Intro">
|
||||||
<h1 class="logo header-Content content">
|
<h1 class="logo header-Content content">
|
||||||
<span class="hidden">Neocities.org</span>
|
<span class="hidden">Neocities.org</span>
|
||||||
<img src="/img/neocities-Logo.png" alt="Neocities.org" />
|
<img src="/img/neocities-Logo.png" alt="Neocities.org" />
|
||||||
</h1>
|
</h1>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<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">
|
||||||
<li class="intro-Social">
|
<div class="col col-40">
|
||||||
<span class="intro-Icon"></span>
|
<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>
|
||||||
<h3 class="delta">Share your web creation with the world</h3>
|
</div>
|
||||||
<p class="base">
|
<div class="col col-60">
|
||||||
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.
|
<div class="title">
|
||||||
</p>
|
<a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">
|
||||||
</li>
|
SUPPORT OUR
|
||||||
<li class="intro-Tools">
|
<img src="/img/kickstarterlogo.png" style="width: 280px; display:block; margin: 11px 0px;">
|
||||||
<span class="intro-Icon"></span>
|
<%= kickstarter_days_remaining %> DAYS LEFT
|
||||||
<h3 class="delta">Powerful new features to help you build</h3>
|
</a>
|
||||||
<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.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</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>
|
</div>
|
||||||
<% else %>
|
<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>
|
||||||
<form id="createSiteForm" class="signup-Form" onsubmit="return false">
|
</div>
|
||||||
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
|
</div>
|
||||||
<fieldset class="content">
|
<!--<ul class="intro-List">
|
||||||
<h2 class="gamma">Sign up for free</h2>
|
<li class="intro-Social">
|
||||||
<hr />
|
<span class="intro-Icon"></span>
|
||||||
<div class="siteCreateInputs">
|
<h3 class="delta">Share your web creation with the world</h3>
|
||||||
<label for="create-Input">User Name</label>
|
<p class="base">
|
||||||
<input type="text" name="prevent_autofill_username" id="prevent_autofill_username" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
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.
|
||||||
<input type="password" name="prevent_autofill_password" id="prevent_autofill_password" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
</p>
|
||||||
<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" />
|
</li>
|
||||||
<label for="create-Input" id="domain-name">.neocities.org</label>
|
<li class="intro-Tools">
|
||||||
|
<span class="intro-Icon"></span>
|
||||||
|
<h3 class="delta">Powerful new features to help you build</h3>
|
||||||
|
<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.
|
||||||
|
</li>
|
||||||
|
</ul>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="tags-input">Tags (your interests, site topics)</label>
|
<div class="col signup-Area">
|
||||||
<input type="text" class="input-Area" id="tags-input" name="new_tags_string" placeholder="art, videogames, food, music, programming, gardening, cats" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
<% if signed_in? %>
|
||||||
|
|
||||||
<div class="col col-50" style="padding-left:0;">
|
<div class="signup-Form">
|
||||||
<label for="password-input">
|
<div class="content">
|
||||||
Password
|
<h3 class="gamma txt-Center">Build your Website!</h3>
|
||||||
</label>
|
</div>
|
||||||
<input type="password" class="input-Area" id="password-input"
|
<p class="txt-Center">
|
||||||
name="password" placeholder="password"
|
Go to your dashboard to<br> start editing your website!
|
||||||
data-placement="left" data-trigger="manual"
|
</p>
|
||||||
autocapitalize="off" autocorrect="off" autocomplete="off" />
|
<br />
|
||||||
</div>
|
<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="false">
|
||||||
|
<fieldset class="content">
|
||||||
|
<h2 class="gamma">Sign up for free</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>
|
||||||
|
|
||||||
<div class="col col-50">
|
<label for="tags-input">Tags (your interests, site topics)</label>
|
||||||
<label for="email-input">
|
<input type="text" class="input-Area" id="tags-input" name="new_tags_string" placeholder="art, videogames, food, music, programming, gardening, cats" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||||
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;">
|
<div class="col col-50" style="padding-left:0;">
|
||||||
<label>
|
<label for="password-input">
|
||||||
Confirm you are human
|
Password
|
||||||
</label>
|
</label>
|
||||||
<div id="captcha-input" class="g-recaptcha"
|
<input type="password" class="input-Area" id="password-input"
|
||||||
data-sitekey="<%= $config['recaptcha_public_key'] %>"
|
name="password" placeholder="password"
|
||||||
data-theme="dark" data-placement="left" data-trigger="manual">
|
data-placement="left" data-trigger="manual"
|
||||||
</div>
|
autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col col-50">
|
<div class="col col-50">
|
||||||
<div style="margin-top: 15px">
|
<label for="email-input">
|
||||||
<input type="submit" value="Create My Site" class="btn-Action float-Right" />
|
Email
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<% end %>
|
<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>
|
||||||
|
|
||||||
</div> <!-- end .col-50 -->
|
<% end %>
|
||||||
|
|
||||||
</div> <!-- end .row -->
|
</div> <!-- end .col-50 -->
|
||||||
|
|
||||||
|
</div> <!-- end .row -->
|
||||||
|
|
||||||
</div> <!-- end .header-Outro -->
|
</div> <!-- end .header-Outro -->
|
||||||
|
|
||||||
|
@ -206,118 +178,118 @@
|
||||||
<main class="content-Base">
|
<main class="content-Base">
|
||||||
|
|
||||||
<div class="section featured-Websites">
|
<div class="section featured-Websites">
|
||||||
<h2 class="delta">Featured Sites</h2>
|
<h2 class="delta">Featured Sites</h2>
|
||||||
<!--
|
<!--
|
||||||
<div class="nav prev"></div>
|
<div class="nav prev"></div>
|
||||||
-->
|
-->
|
||||||
<ul class="website-Gallery hp-Gallery">
|
<ul class="website-Gallery hp-Gallery">
|
||||||
<% Site.featured.each do |site| %>
|
<% Site.featured.each do |site| %>
|
||||||
<li>
|
<li>
|
||||||
<a href="<%= site.uri %>" title="<%= site.title %>" target="_blank">
|
<a href="<%= site.uri %>" title="<%= site.title %>" target="_blank">
|
||||||
<img src="<%= site.screenshot_url 'index.html', '210x158' %>" class="neo-SS" alt="<%= site.title %>" />
|
<img src="<%= site.screenshot_url 'index.html', '210x158' %>" class="neo-SS" alt="<%= site.title %>" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<!--
|
<!--
|
||||||
<div class="nav next"></div>
|
<div class="nav next"></div>
|
||||||
-->
|
-->
|
||||||
<a href="/browse" class="btn-Action float-Right">Browse all sites</a>
|
<a href="/browse" class="btn-Action float-Right">Browse all sites</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section previews">
|
<div class="section previews">
|
||||||
<h2 class="delta">Our mission: To make the web fun again by giving you back control of how you express yourself online.</h2>
|
<h2 class="delta">Our mission: To make the web fun again by giving you back control of how you express yourself online.</h2>
|
||||||
|
|
||||||
<div class="row content">
|
<div class="row content">
|
||||||
<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 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 text">
|
|
||||||
<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>Using tags, our version of web rings, you can easily discover new sites related to your interests.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row content">
|
|
||||||
<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">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row content right">
|
|
||||||
<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">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="section features">
|
<div class="row content right">
|
||||||
<div class="row">
|
<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="col col-50 text">
|
||||||
|
<h3><i class="fa fa-globe"></i> It's time to bring back web surfing.</h3>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row content">
|
||||||
|
<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">
|
||||||
|
<h3><i class="fa fa-user-plus"></i> Follow your favorite Neocities sites</h3>
|
||||||
|
<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 class="row content right">
|
||||||
|
<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">
|
||||||
|
<h3><i class="fa fa-comments-o"></i> Web creativity plus community</h3>
|
||||||
|
<p>Interact with your favorite web builders by posting comments, and sharing their sites on your social network of choice.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="section features">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-50">
|
||||||
|
<h3>
|
||||||
|
<i class="fa fa-eye-slash"></i>Zero advertising
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
Neocities will never sell your personal data or embed advertising on your site. Instead, we are funded directly by our community through <a href="/plan">supporter plans</a> and <a href="/donate">donations</a>. This allows us to base all our decisions on making the best possible web building experience for you, rather than on appeasing ad companies.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col col-50">
|
||||||
|
<h3><i class="fa fa-tachometer"></i>More space, speed, and security</h3>
|
||||||
|
<p>Neocities now uses distributed, globally-cached web servers in datacenters all over the world to serve your site. Whether it’s your personal home page or a busy professional site, your site loads fast. And if you need more space, <a href="/plan">we've got you covered</a>. We also provide Snowden-grade SSL cryptography on all sites, preventing snoops from seeing what you browse.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-50">
|
||||||
|
<h3><i class="fa fa-wrench"></i>Developer tools</h3>
|
||||||
|
<p>We have an in-browser HTML editor, easy file uploading, WebDAV publishing, support for custom domains, RSS feeds for every site, powerful APIs for building developer applications, and much more!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-50">
|
||||||
|
<h3><i class="fa fa-university"></i>Open Company</h3>
|
||||||
|
<p>Neocities is a member of the <a href="http://www.opencompany.org/" target="_blank">Open Company Initative</a>, working to help improve trustability in tech companies. We <a href="https://github.com/neocities" target="_blank">publish</a> the code that powers the site for inspection, and strive for openness in our company's operations. We want to win your trust—not lock you in.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-100">
|
||||||
|
<div class="row press">
|
||||||
|
<a href="http://www.wired.com/2013/07/neocities/" class="logo wired"></a>
|
||||||
|
<a href="http://www.fastcodesign.com/3037028/why-the-internet-is-time-traveling-back-to-1994" class="logo fastco"></a>
|
||||||
|
<a href="http://motherboard.vice.com/blog/neocities-is-recreating-the-garish-web-10-creativity-of-geocities" class="logo vice"></a>
|
||||||
|
<a href="http://arstechnica.com/tech-policy/2014/05/web-host-gives-fcc-a-28-8kbps-slow-lane-in-net-neutrality-protest/" class="logo ars"></a>
|
||||||
|
<!--<a href="/press" class="more">See all press »</a>-->
|
||||||
|
</div>
|
||||||
|
<div class="row quote">
|
||||||
<h3>
|
<h3>
|
||||||
<i class="fa fa-eye-slash"></i>Zero advertising
|
"Designed as a 21st century reincarnation of GeoCities, Neocities lets you make your own site for free. <strong>And it just might spark a renaissance of creativity online.</strong>"
|
||||||
|
<br />
|
||||||
|
<cite>— Matthew Guay, <a href="http://web.appstorm.net/reviews/web-dev/neocities-the-free-place-to-code-your-own-site-from-scratch" target="_blank">AppStorm</a>. <a href="/press">View All Press »</a></cite>
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
|
||||||
Neocities will never sell your personal data or embed advertising on your site. Instead, we are funded directly by our community through <a href="/plan">supporter plans</a> and <a href="/donate">donations</a>. This allows us to base all our decisions on making the best possible web building experience for you, rather than on appeasing ad companies.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col col-50">
|
|
||||||
<h3><i class="fa fa-tachometer"></i>More space, speed, and security</h3>
|
|
||||||
<p>Neocities now uses distributed, globally-cached web servers in datacenters all over the world to serve your site. Whether it’s your personal home page or a busy professional site, your site loads fast. And if you need more space, <a href="/plan">we've got you covered</a>. We also provide Snowden-grade SSL cryptography on all sites, preventing snoops from seeing what you browse.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="row">
|
<% # erb :'plan/_pricing' %>
|
||||||
<div class="col col-50">
|
|
||||||
<h3><i class="fa fa-wrench"></i>Developer tools</h3>
|
|
||||||
<p>We have an in-browser HTML editor, easy file uploading, WebDAV publishing, support for custom domains, RSS feeds for every site, powerful APIs for building developer applications, and much more!</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col col-50">
|
<section class="section bottom-signup">
|
||||||
<h3><i class="fa fa-university"></i>Open Company</h3>
|
<h2>What are you waiting for? <a href="#new">Start building your web site now!</a></h2>
|
||||||
<p>Neocities is a member of the <a href="http://www.opencompany.org/" target="_blank">Open Company Initative</a>, working to help improve trustability in tech companies. We <a href="https://github.com/neocities" target="_blank">publish</a> the code that powers the site for inspection, and strive for openness in our company's operations. We want to win your trust—not lock you in.</p>
|
</section>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col col-100">
|
|
||||||
<div class="row press">
|
|
||||||
<a href="http://www.wired.com/2013/07/neocities/" class="logo wired"></a>
|
|
||||||
<a href="http://www.fastcodesign.com/3037028/why-the-internet-is-time-traveling-back-to-1994" class="logo fastco"></a>
|
|
||||||
<a href="http://motherboard.vice.com/blog/neocities-is-recreating-the-garish-web-10-creativity-of-geocities" class="logo vice"></a>
|
|
||||||
<a href="http://arstechnica.com/tech-policy/2014/05/web-host-gives-fcc-a-28-8kbps-slow-lane-in-net-neutrality-protest/" class="logo ars"></a>
|
|
||||||
<!--<a href="/press" class="more">See all press »</a>-->
|
|
||||||
</div>
|
|
||||||
<div class="row quote">
|
|
||||||
<h3>
|
|
||||||
"Designed as a 21st century reincarnation of GeoCities, Neocities lets you make your own site for free. <strong>And it just might spark a renaissance of creativity online.</strong>"
|
|
||||||
<br />
|
|
||||||
<cite>— Matthew Guay, <a href="http://web.appstorm.net/reviews/web-dev/neocities-the-free-place-to-code-your-own-site-from-scratch" target="_blank">AppStorm</a>. <a href="/press">View All Press »</a></cite>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<% # erb :'plan/_pricing' %>
|
|
||||||
|
|
||||||
<section class="section bottom-signup">
|
|
||||||
<h2>What are you waiting for? <a href="#new">Start building your web site now!</a></h2>
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="footer-Base" role="contentinfo">
|
<footer class="footer-Base" role="contentinfo">
|
||||||
|
@ -358,47 +330,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%== erb :'_footer', layout: false %>
|
<%== erb :'_footer', layout: false %>
|
||||||
</footer>
|
</footer>
|
||||||
|
</div>
|
||||||
</div>
|
<%== erb :'_index_signup_script', layout: false %>
|
||||||
<script src="/js/app.min.js"></script>
|
</body>
|
||||||
<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>
|
|
||||||
</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>
|
|
@ -130,7 +130,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.
|
||||||
|
|
28
views/search.erb
Normal file
28
views/search.erb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<div class="header-Outro">
|
||||||
|
<div class="row content single-Col txt-Center">
|
||||||
|
<h1>Search Neocities Sites</h1>
|
||||||
|
<h3 class="subtitle"></h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content txt-Center single-Col misc-page">
|
||||||
|
<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: 290px">
|
||||||
|
</fieldset>
|
||||||
|
<input class="btn-Action" type="submit" value="Search">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>Search powered by <a href="https://duckduckgo.com">DuckDuckGo</a>, a search engine that cares about your privacy as much as we do.</p>
|
||||||
|
</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>
|
|
@ -75,4 +75,4 @@ $(document).ready(function() {
|
||||||
return location.hash = $(e.target).attr('href').substr(1);
|
return location.hash = $(e.target).attr('href').substr(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -81,4 +81,4 @@
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
@ -29,4 +29,4 @@
|
||||||
<p>If you want to make this the index page (and an index page doesn't exist), name it <strong>index.html</strong>.</p>
|
<p>If you want to make this the index page (and an index page doesn't exist), name it <strong>index.html</strong>.</p>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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">
|
||||||
|
@ -70,7 +75,7 @@
|
||||||
</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"><i class="fa fa-save"></i>Save</a>
|
||||||
<a class="btn-Action" href="//<%= current_site.host %>/<%= @filename %>" target="_blank">View</a>
|
<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>
|
||||||
<!-- <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;">
|
||||||
|
@ -85,8 +90,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>
|
||||||
|
|
||||||
|
|
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>
|
|
@ -2,9 +2,9 @@ Hello,
|
||||||
|
|
||||||
We're writing to let you know that Neocities has had an issue charging your card. We will try again automatically in a few days, but we just wanted to give you a heads up in case something is wrong with your card.
|
We're writing to let you know that Neocities has had an issue charging your card. We will try again automatically in a few days, but we just wanted to give you a heads up in case something is wrong with your card.
|
||||||
|
|
||||||
We will retry over the next few weeks. If we can't make a successful charge, your account will be downgraded from supporter to the free plan.
|
We will retry over the next few weeks. If we can't make a successful charge, we will automatically switch your account over to the free plan.
|
||||||
|
|
||||||
If you need to change the card on file, you can do so on the settings page. If you run into any issues, please contact us at https://neocities.org/contact.
|
If you need to change the card on file, you can do so on your account settings page. If you run into any issues, please contact us at https://neocities.org/contact.
|
||||||
|
|
||||||
Regards,
|
Regards,
|
||||||
- The Neocities Team
|
- The Neocities Team
|
||||||
|
|
|
@ -1,12 +1,28 @@
|
||||||
<%= request.request_method %> <%= request.path %>
|
#### Route
|
||||||
|
<%== request.request_method %> <%== request.path %>
|
||||||
|
|
||||||
<% if current_site %>
|
<% if current_site %>
|
||||||
Site: <%= current_site.username %>
|
#### Site
|
||||||
Email: <%= current_site.email %>
|
<%== current_site.username %>
|
||||||
|
<% if current_site.email %>
|
||||||
|
Email: <%== current_site.email %>
|
||||||
|
<% else %>
|
||||||
|
User does not have an email address.
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
Not a logged in session.
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
Params:
|
#### Params
|
||||||
<%= params.inspect %>
|
<%== params.inspect %>
|
||||||
|
|
||||||
Backtrace:
|
#### Session
|
||||||
<%= env['sinatra.error'].backtrace.join("\n") %>
|
|
||||||
|
<%== session.inspect %>
|
||||||
|
|
||||||
|
#### HTTP REFERRER
|
||||||
|
|
||||||
|
<%== request.referrer %>
|
||||||
|
|
||||||
|
#### Backtrace
|
||||||
|
<%== env['sinatra.error'].backtrace.join("\n") %>
|
||||||
|
|
27
workers/archive_worker.rb
Normal file
27
workers/archive_worker.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
require 'sidekiq/api'
|
||||||
|
|
||||||
|
class ArchiveWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
sidekiq_options queue: :archive, retry: 2, backtrace: true
|
||||||
|
|
||||||
|
def perform(site_id)
|
||||||
|
site = Site[site_id]
|
||||||
|
return if site.nil? || site.is_banned? || site.is_deleted
|
||||||
|
|
||||||
|
if site.site_files_dataset.count > 1000
|
||||||
|
logger.info 'skipping #{site_id} (#{site.username}) due to > 1000 files'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
queue = Sidekiq::Queue.new self.class.sidekiq_options_hash['queue']
|
||||||
|
logger.info "JOB ID: #{jid} #{site_id.inspect}"
|
||||||
|
queue.each do |job|
|
||||||
|
if job.args == [site_id] && job.jid != jid
|
||||||
|
logger.info "DELETING #{job.jid} #{job.args.inspect}"
|
||||||
|
job.delete
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
site.archive!
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,12 +3,13 @@ class EmailWorker
|
||||||
sidekiq_options queue: :emails, retry: 10, backtrace: true
|
sidekiq_options queue: :emails, retry: 10, backtrace: true
|
||||||
|
|
||||||
def perform(args={})
|
def perform(args={})
|
||||||
|
raise 'no' if ENV['RACK_ENV'].nil? || ENV['RACK_ENV'] == 'development'
|
||||||
unsubscribe_token = Site.email_unsubscribe_token args['to']
|
unsubscribe_token = Site.email_unsubscribe_token args['to']
|
||||||
|
|
||||||
if args['no_footer']
|
if args['no_footer']
|
||||||
footer = ''
|
footer = ''
|
||||||
else
|
else
|
||||||
footer = "\n\n---\nYou are receiving this email because you have a Neocities site. If you would like to subscribe from Neocities emails, just visit this url:\nhttps://neocities.org/settings/unsubscribe_email?email=#{Rack::Utils.escape args['to']}&token=#{unsubscribe_token}"
|
footer = "\n\n---\nYou are receiving this email because you have a Neocities site. If you would like to unsubscribe from Neocities emails, just visit this url:\nhttps://neocities.org/settings/unsubscribe_email?email=#{Rack::Utils.escape args['to']}&token=#{unsubscribe_token}"
|
||||||
end
|
end
|
||||||
|
|
||||||
Mail.deliver do
|
Mail.deliver do
|
||||||
|
|
23
workers/purge_cache_order_worker.rb
Normal file
23
workers/purge_cache_order_worker.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
class PurgeCacheOrderWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
sidekiq_options queue: :purgecacheorder, retry: 1000, backtrace: true, average_scheduled_poll_interval: 1
|
||||||
|
|
||||||
|
sidekiq_retry_in do |count|
|
||||||
|
return 10 if count < 10
|
||||||
|
180
|
||||||
|
end
|
||||||
|
|
||||||
|
RESOLVER = Dnsruby::Resolver.new
|
||||||
|
|
||||||
|
def perform(username, path)
|
||||||
|
if ENV['RACK_ENV'] == 'test'
|
||||||
|
proxy_ips = ['10.0.0.1', '10.0.0.2']
|
||||||
|
else
|
||||||
|
proxy_ips = RESOLVER.query($config['cache_purge_ips_uri']).answer.collect {|a| a.address.to_s}
|
||||||
|
end
|
||||||
|
|
||||||
|
proxy_ips.each do |proxy_ip|
|
||||||
|
PurgeCacheWorker.perform_async proxy_ip, username, path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,22 +1,25 @@
|
||||||
|
require 'open-uri'
|
||||||
|
|
||||||
class PurgeCacheWorker
|
class PurgeCacheWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
sidekiq_options queue: :purgecache, retry: 10, backtrace: true
|
sidekiq_options queue: :purgecache, retry: 1000, backtrace: false, average_scheduled_poll_interval: 1
|
||||||
|
|
||||||
|
sidekiq_retry_in do |count|
|
||||||
def perform(payload)
|
return 10 if count < 10
|
||||||
# :nocov:
|
180
|
||||||
attempt = 0
|
|
||||||
begin
|
|
||||||
attempt += 1
|
|
||||||
$pubsub_pool.with do |redis|
|
|
||||||
redis.publish 'purgecache', payload.to_json
|
|
||||||
end
|
|
||||||
rescue Redis::BaseConnectionError => error
|
|
||||||
raise if attempt > 3
|
|
||||||
puts "pubsub error: #{error}, retrying in 1s"
|
|
||||||
sleep 1
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
# :nocov:
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
def perform(proxy_ip, username, path)
|
||||||
|
# Must always have a forward slash
|
||||||
|
path = '/' + path if path[0] != '/'
|
||||||
|
|
||||||
|
url = Addressable::URI.encode_component(
|
||||||
|
"http://#{proxy_ip}/:cache/purge#{path}",
|
||||||
|
Addressable::URI::CharacterClasses::QUERY
|
||||||
|
)
|
||||||
|
begin
|
||||||
|
RestClient.get(url, host: URI::encode("#{username}.neocities.org"))
|
||||||
|
rescue RestClient::ResourceNotFound
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue