mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 09:12:35 +02:00
fix for site tags referencing followed counts
This commit is contained in:
commit
d264672b51
114 changed files with 4538 additions and 1052 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -34,3 +34,4 @@ files/sslsites.zip
|
|||
.vagrant
|
||||
public/banned_sites
|
||||
public/deleted_sites
|
||||
tests/stat_logs/*
|
||||
|
|
6
Gemfile
6
Gemfile
|
@ -26,6 +26,10 @@ gem 'thread'
|
|||
gem 'scrypt'
|
||||
gem 'rack-cache'
|
||||
gem 'rest-client'
|
||||
gem 'geoip'
|
||||
gem 'io-extra', require: 'io/extra'
|
||||
gem 'rye'
|
||||
gem 'dnsruby'
|
||||
|
||||
platform :mri, :rbx do
|
||||
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic
|
||||
|
@ -72,8 +76,10 @@ group :test do
|
|||
gem 'rack_session_access', require: nil
|
||||
gem 'webmock', require: nil
|
||||
gem 'stripe-ruby-mock', '~> 2.0.1', require: 'stripe_mock'
|
||||
gem 'timecop'
|
||||
|
||||
platform :mri, :rbx do
|
||||
gem 'simplecov', require: nil
|
||||
gem 'm'
|
||||
end
|
||||
end
|
||||
|
|
64
Gemfile.lock
64
Gemfile.lock
|
@ -9,6 +9,8 @@ GEM
|
|||
tzinfo (~> 1.1)
|
||||
addressable (2.3.7)
|
||||
ago (0.1.5)
|
||||
annoy (0.5.6)
|
||||
highline (>= 1.5.0)
|
||||
ansi (1.4.3)
|
||||
autoparse (0.3.3)
|
||||
addressable (>= 2.3.1)
|
||||
|
@ -20,15 +22,15 @@ GEM
|
|||
byebug (2.7.0)
|
||||
columnize (~> 0.3)
|
||||
debugger-linecache (~> 1.2)
|
||||
capybara (2.4.1)
|
||||
capybara (2.4.4)
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (~> 2.0)
|
||||
capybara_minitest_spec (1.0.1)
|
||||
capybara_minitest_spec (1.0.5)
|
||||
capybara (>= 2)
|
||||
minitest (>= 2)
|
||||
minitest (>= 4)
|
||||
celluloid (0.15.2)
|
||||
timers (~> 1.1.0)
|
||||
climate_control (0.0.3)
|
||||
|
@ -47,9 +49,11 @@ GEM
|
|||
rack (>= 1.1.0)
|
||||
uuidtools (~> 2.1.1)
|
||||
debugger-linecache (1.2.0)
|
||||
dnsruby (1.58.0)
|
||||
docile (1.1.3)
|
||||
domain_name (0.5.23)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
drydock (0.6.9)
|
||||
erubis (2.7.0)
|
||||
extlib (0.9.16)
|
||||
fabrication (2.11.0)
|
||||
|
@ -62,6 +66,7 @@ GEM
|
|||
ffi (>= 1.0.0)
|
||||
rake
|
||||
filesize (0.0.3)
|
||||
geoip (1.5.0)
|
||||
google-api-client (0.7.1)
|
||||
addressable (>= 2.3.2)
|
||||
autoparse (>= 0.3.3)
|
||||
|
@ -74,10 +79,12 @@ GEM
|
|||
signet (>= 0.5.0)
|
||||
uuidtools (>= 2.1.0)
|
||||
hashie (2.0.5)
|
||||
highline (1.7.2)
|
||||
hiredis (0.5.0)
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
i18n (0.6.9)
|
||||
io-extra (1.2.8)
|
||||
jimson-temp (0.9.5)
|
||||
blankslate (>= 3.1.2)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -89,6 +96,9 @@ GEM
|
|||
kgio (2.9.2)
|
||||
launchy (2.4.2)
|
||||
addressable (~> 2.3)
|
||||
m (1.3.4)
|
||||
method_source (>= 0.6.7)
|
||||
rake (>= 0.9.2.2)
|
||||
magic (0.2.6)
|
||||
ffi (>= 0.6.3)
|
||||
mail (2.5.4)
|
||||
|
@ -97,8 +107,8 @@ GEM
|
|||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.6.0)
|
||||
minitest (5.3.1)
|
||||
mini_portile (0.6.2)
|
||||
minitest (5.6.1)
|
||||
minitest-reporters (1.0.2)
|
||||
ansi
|
||||
builder
|
||||
|
@ -106,14 +116,17 @@ GEM
|
|||
powerbar
|
||||
mocha (1.0.0)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.10.1)
|
||||
multi_json (1.11.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)
|
||||
nokogiri (1.6.3.1)
|
||||
mini_portile (= 0.6.0)
|
||||
nokogiri (1.6.6.2)
|
||||
mini_portile (~> 0.6.0)
|
||||
pg (0.17.1)
|
||||
phantomjs (1.9.7.1)
|
||||
poltergeist (1.5.1)
|
||||
poltergeist (1.6.0)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -131,14 +144,14 @@ GEM
|
|||
pry (~> 0.9.12)
|
||||
puma (2.8.1)
|
||||
rack (>= 1.1, < 2.0)
|
||||
rack (1.5.2)
|
||||
rack (1.6.0)
|
||||
rack-cache (1.2)
|
||||
rack (>= 0.4)
|
||||
rack-protection (1.5.2)
|
||||
rack
|
||||
rack-recaptcha (0.6.6)
|
||||
json
|
||||
rack-test (0.6.2)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rack_session_access (0.1.1)
|
||||
builder (>= 2.0.0)
|
||||
|
@ -157,7 +170,14 @@ GEM
|
|||
mime-types (>= 1.16, < 3.0)
|
||||
netrc (~> 0.7)
|
||||
retriable (1.4.1)
|
||||
rmagick (2.13.3)
|
||||
rmagick (2.15.0)
|
||||
rye (0.9.13)
|
||||
annoy
|
||||
docile (>= 1.0.1)
|
||||
highline (>= 1.5.1)
|
||||
net-scp (>= 1.0.2)
|
||||
net-ssh (>= 2.0.13)
|
||||
sysinfo (>= 0.8.1)
|
||||
safe_yaml (1.0.4)
|
||||
sass (3.3.8)
|
||||
screencap (0.1.1)
|
||||
|
@ -195,7 +215,8 @@ GEM
|
|||
sinatra (>= 1.0.0)
|
||||
sinatra-xsendfile (0.4.2)
|
||||
sinatra (>= 0.9.1)
|
||||
slop (3.5.0)
|
||||
slop (3.6.0)
|
||||
storable (0.8.9)
|
||||
stripe (1.15.0)
|
||||
json (~> 1.8.1)
|
||||
mime-types (>= 1.25, < 3.0)
|
||||
|
@ -204,9 +225,13 @@ GEM
|
|||
dante (>= 0.2.0)
|
||||
jimson-temp
|
||||
stripe (>= 1.15.0)
|
||||
sysinfo (0.8.1)
|
||||
drydock
|
||||
storable
|
||||
thread (0.1.4)
|
||||
thread_safe (0.3.4)
|
||||
tilt (1.4.1)
|
||||
timecop (0.7.4)
|
||||
timers (1.1.0)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
|
@ -224,7 +249,9 @@ GEM
|
|||
webmock (1.17.4)
|
||||
addressable (>= 2.2.7)
|
||||
crack (>= 0.3.2)
|
||||
websocket-driver (0.3.4)
|
||||
websocket-driver (0.5.4)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
zipruby (0.3.6)
|
||||
|
@ -238,15 +265,19 @@ DEPENDENCIES
|
|||
capybara_minitest_spec
|
||||
cocaine
|
||||
dav4rack
|
||||
dnsruby
|
||||
erubis
|
||||
fabrication
|
||||
faker
|
||||
filesize
|
||||
geoip
|
||||
google-api-client
|
||||
hiredis
|
||||
io-extra
|
||||
jdbc-postgres
|
||||
jruby-openssl
|
||||
json
|
||||
m
|
||||
magic
|
||||
mail
|
||||
minitest
|
||||
|
@ -267,6 +298,7 @@ DEPENDENCIES
|
|||
rest-client
|
||||
rmagick
|
||||
ruby-debug
|
||||
rye
|
||||
sass
|
||||
screencap
|
||||
scrypt
|
||||
|
@ -282,5 +314,9 @@ DEPENDENCIES
|
|||
stripe-ruby-mock (~> 2.0.1)
|
||||
thread
|
||||
tilt
|
||||
timecop
|
||||
webmock
|
||||
zipruby
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.2
|
||||
|
|
41
Rakefile
41
Rakefile
|
@ -31,40 +31,11 @@ end
|
|||
|
||||
desc "parse logs"
|
||||
task :parse_logs => [:environment] do
|
||||
Dir[File.join($config['logs_path'], '*.log')].each do |log_path|
|
||||
hits = {}
|
||||
visits = {}
|
||||
visit_ips = {}
|
||||
|
||||
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
|
||||
Stat.prune!
|
||||
StatLocation.prune!
|
||||
StatReferrer.prune!
|
||||
StatPath.prune!
|
||||
Stat.parse_logfiles $config['logs_path']
|
||||
end
|
||||
|
||||
desc 'Update banned IPs list'
|
||||
|
@ -223,7 +194,7 @@ end
|
|||
desc 'prime_space_used'
|
||||
task :prime_space_used => [:environment] do
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
3
app.rb
3
app.rb
|
@ -36,6 +36,7 @@ before do
|
|||
end
|
||||
|
||||
not_found do
|
||||
@title = 'Not Found'
|
||||
erb :'not_found'
|
||||
end
|
||||
|
||||
|
@ -44,7 +45,7 @@ error do
|
|||
from: 'web@neocities.org',
|
||||
to: 'errors@neocities.org',
|
||||
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
|
||||
})
|
||||
|
||||
|
|
52
app/admin.rb
52
app/admin.rb
|
@ -5,6 +5,56 @@ get '/admin' do
|
|||
erb :'admin'
|
||||
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
|
||||
require_admin
|
||||
site = Site[username: params[:username]]
|
||||
|
@ -18,7 +68,7 @@ post '/admin/banip' do
|
|||
flash[:error] = 'IP is blank, cannot continue'
|
||||
redirect '/admin'
|
||||
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!}
|
||||
flash[:error] = "#{sites.length} sites have been banned."
|
||||
redirect '/admin'
|
||||
|
|
17
app/api.rb
17
app/api.rb
|
@ -7,6 +7,7 @@ end
|
|||
|
||||
post '/api/upload' do
|
||||
require_api_credentials
|
||||
|
||||
files = []
|
||||
params.each do |k,v|
|
||||
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'
|
||||
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|
|
||||
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"
|
||||
|
@ -32,13 +37,7 @@ post '/api/upload' do
|
|||
end
|
||||
end
|
||||
|
||||
results = []
|
||||
files.each do |file|
|
||||
results << current_site.store_file(file[:filename], file[:tempfile])
|
||||
end
|
||||
|
||||
current_site.increment_changed_count if results.include?(true)
|
||||
|
||||
results = current_site.store_files files
|
||||
api_success 'your file(s) have been successfully uploaded'
|
||||
end
|
||||
|
||||
|
@ -53,6 +52,10 @@ post '/api/delete' do
|
|||
api_error 400, 'bad_filename', "#{path} is not a valid filename, canceled deleting"
|
||||
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)
|
||||
api_error 400, 'missing_files', "#{path} was not found on your site, canceled deleting"
|
||||
end
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
get '/browse/?' do
|
||||
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
||||
site_dataset = browse_sites_dataset
|
||||
site_dataset = site_dataset.paginate @current_page, Site::BROWSE_PAGINATION_LENGTH
|
||||
@page_count = site_dataset.page_count || 1
|
||||
@sites = site_dataset.all
|
||||
erb :browse
|
||||
end
|
||||
|
||||
def browse_sites_dataset
|
||||
@current_page = params[:current_page]
|
||||
@current_page = @current_page.to_i
|
||||
@current_page = 1 if @current_page == 0
|
||||
|
||||
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
||||
|
||||
if is_education?
|
||||
site_dataset = education_sites_dataset
|
||||
else
|
||||
site_dataset = browse_sites_dataset
|
||||
end
|
||||
|
||||
site_dataset = site_dataset.paginate @current_page, Site::BROWSE_PAGINATION_LENGTH
|
||||
@page_count = site_dataset.page_count || 1
|
||||
@sites = site_dataset.all
|
||||
if params[:tag]
|
||||
@title = "Sites tagged #{params[:tag]}"
|
||||
end
|
||||
erb :browse
|
||||
end
|
||||
|
||||
def education_sites_dataset
|
||||
site_dataset = Site.filter is_deleted: false
|
||||
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
||||
params[:tag] = current_site.tags.first.name
|
||||
site_dataset.where! ['tags.name = ?', params[:tag]]
|
||||
end
|
||||
|
||||
def browse_sites_dataset
|
||||
site_dataset = Site.filter(is_deleted: false, is_banned: false, is_crashing: false).filter(site_changed: true)
|
||||
|
||||
if current_site
|
||||
|
@ -27,6 +43,19 @@ def browse_sites_dataset
|
|||
end
|
||||
|
||||
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'
|
||||
site_dataset.where!{views > 100}
|
||||
site_dataset.order!(:hits.desc, :site_updated_at.desc)
|
||||
|
@ -50,16 +79,19 @@ def browse_sites_dataset
|
|||
params[:sort_by] = 'views'
|
||||
site_dataset.order!(:views.desc, :site_updated_at.desc)
|
||||
else
|
||||
params[:sort_by] = 'last_updated'
|
||||
site_dataset.where!{views > 100}
|
||||
site_dataset.order!(:site_updated_at.desc, :views.desc)
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
site_dataset.where! ['sites.is_nsfw = ?', (params[:is_nsfw] == 'true' ? true : false)]
|
||||
|
||||
if params[:tag]
|
||||
site_dataset = site_dataset.association_join(:tags).select_all(:sites)
|
||||
site_dataset.inner_join! :sites_tags, :site_id => :id
|
||||
site_dataset.inner_join! :tags, :id => :sites_tags__tag_id
|
||||
site_dataset.where! ['tags.name = ?', params[:tag]]
|
||||
site_dataset.where! ['tags.is_nsfw = ?', (params[:is_nsfw] == 'true' ? true : false)]
|
||||
end
|
||||
|
|
|
@ -16,9 +16,11 @@ def new_recaptcha_valid?
|
|||
end
|
||||
end
|
||||
|
||||
CREATE_MATCH_REGEX = /^username$|^password$|^email$|^new_tags_string$|^is_education$/
|
||||
|
||||
post '/create_validate_all' do
|
||||
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
|
||||
|
||||
|
@ -33,11 +35,12 @@ end
|
|||
post '/create_validate' do
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
site = Site.new(params[:field] => params[:value])
|
||||
site.is_education = params[:is_education]
|
||||
site.valid?
|
||||
|
||||
field_sym = params[:field].to_sym
|
||||
|
@ -51,14 +54,23 @@ end
|
|||
|
||||
post '/create' do
|
||||
content_type :json
|
||||
require_unbanned_ip
|
||||
|
||||
if banned?(true)
|
||||
signout
|
||||
session[:banned] = true if !session[:banned]
|
||||
|
||||
flash[:error] = 'There was an error, please <a href="/contact">contact support</a> to log in.'
|
||||
redirect '/'
|
||||
end
|
||||
|
||||
dashboard_if_signed_in
|
||||
|
||||
@site = Site.new(
|
||||
username: params[:username],
|
||||
password: params[:password],
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -85,4 +97,4 @@ post '/create' do
|
|||
|
||||
session[:id] = @site.id
|
||||
{result: 'ok'}.to_json
|
||||
end
|
||||
end
|
||||
|
|
14
app/index.rb
14
app/index.rb
|
@ -2,6 +2,8 @@ get '/?' do
|
|||
if current_site
|
||||
require_login
|
||||
|
||||
redirect '/dashboard' if current_site.is_education
|
||||
|
||||
@suggestions = current_site.suggestions
|
||||
|
||||
@current_page = params[:current_page].to_i
|
||||
|
@ -34,7 +36,7 @@ get '/?' do
|
|||
@sites_count = SimpleCache.get :sites_count
|
||||
end
|
||||
|
||||
erb :index, layout: false
|
||||
erb :index, layout: :index_layout
|
||||
end
|
||||
|
||||
get '/welcome' do
|
||||
|
@ -43,6 +45,11 @@ get '/welcome' do
|
|||
erb :'welcome', locals: {site: current_site}
|
||||
end
|
||||
|
||||
get '/education' do
|
||||
redirect '/' if signed_in?
|
||||
erb :education, layout: :index_layout
|
||||
end
|
||||
|
||||
get '/tutorials' do
|
||||
erb :'tutorials'
|
||||
end
|
||||
|
@ -68,5 +75,10 @@ get '/press' do
|
|||
end
|
||||
|
||||
get '/legal/?' do
|
||||
@title = 'Legal Guide to Neocities'
|
||||
erb :'legal'
|
||||
end
|
||||
|
||||
get '/permanent-web' do
|
||||
erb :'permanent_web'
|
||||
end
|
||||
|
|
|
@ -24,4 +24,9 @@ get '/welcome_mockup' do
|
|||
require_login
|
||||
erb :'welcome_mockup', locals: {site: current_site}
|
||||
end
|
||||
|
||||
get '/stats_mockup' do
|
||||
require_login
|
||||
erb :'stats_mockup', locals: {site: current_site}
|
||||
end
|
||||
# :nocov:
|
|
@ -1,5 +1,6 @@
|
|||
get '/signin/?' do
|
||||
dashboard_if_signed_in
|
||||
@title = 'Sign In'
|
||||
erb :'signin'
|
||||
end
|
||||
|
||||
|
@ -47,4 +48,4 @@ end
|
|||
|
||||
def signout
|
||||
session[:id] = nil
|
||||
end
|
||||
end
|
||||
|
|
73
app/site.rb
73
app/site.rb
|
@ -9,6 +9,8 @@ get '/site/:username/?' do |username|
|
|||
# TODO: There should probably be a "this site was deleted" page.
|
||||
not_found if site.nil? || site.is_banned || site.is_deleted
|
||||
|
||||
redirect '/' if site.is_education
|
||||
|
||||
@title = site.title
|
||||
|
||||
@current_page = params[:current_page]
|
||||
|
@ -16,6 +18,7 @@ get '/site/:username/?' do |username|
|
|||
@current_page = 1 if @current_page == 0
|
||||
|
||||
if params[:event_id]
|
||||
not_found unless params[:event_id].is_integer?
|
||||
event = Event.select(:id).where(id: params[:event_id]).first
|
||||
not_found if event.nil?
|
||||
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}
|
||||
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
|
||||
require_login
|
||||
current_site.editor_theme = params[:editor_theme]
|
||||
|
|
|
@ -9,32 +9,67 @@ get '/site_files/new' do
|
|||
redirect '/site_files/new_page'
|
||||
end
|
||||
|
||||
post '/site_files/create_page' do
|
||||
post '/site_files/create' do
|
||||
require_login
|
||||
@errors = []
|
||||
|
||||
params[:pagefilename].gsub!(/[^a-zA-Z0-9_\-.]/, '')
|
||||
params[:pagefilename].gsub!(/\.html$/i, '')
|
||||
filename = params[:pagefilename] || params[:filename]
|
||||
|
||||
if params[:pagefilename].nil? || params[:pagefilename].strip.empty?
|
||||
@errors << 'You must provide a file name.'
|
||||
halt erb(:'site_files/new_page')
|
||||
filename.gsub!(/[^a-zA-Z0-9_\-.]/, '')
|
||||
|
||||
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
|
||||
|
||||
name = "#{params[:pagefilename]}.html"
|
||||
name = "#{filename}"
|
||||
|
||||
name = "#{params[:dir]}/#{name}" if params[:dir]
|
||||
|
||||
name = current_site.scrubbed_path name
|
||||
|
||||
if current_site.file_exists?(name)
|
||||
@errors << %{Web page "#{name}" already exists! Choose another name.}
|
||||
halt erb(:'site_files/new_page')
|
||||
flash[:error] = %{Web page "#{name}" already exists! Choose another name.}
|
||||
redirect redirect_uri
|
||||
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>.}
|
||||
|
||||
redirect params[:dir] ? "/dashboard?dir=#{Rack::Utils.escape params[:dir]}" : '/dashboard'
|
||||
redirect redirect_uri
|
||||
end
|
||||
|
||||
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!"
|
||||
end
|
||||
|
||||
params[:files].each do |file|
|
||||
file[:filename] = "#{params[:dir]}/#{file[:filename]}" if params[:dir]
|
||||
params[:files].each_with_index do |file,i|
|
||||
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
|
||||
file_upload_response "#{file[:filename]} is too large, upload cancelled."
|
||||
end
|
||||
|
@ -75,21 +124,23 @@ post '/site_files/upload' do
|
|||
file_upload_response "File(s) do not fit in your available space, upload cancelled."
|
||||
end
|
||||
|
||||
results = []
|
||||
params[:files].each do |file|
|
||||
results << current_site.store_file(file[:filename], file[:tempfile])
|
||||
if current_site.too_many_files? params[:files].length
|
||||
file_upload_response "Too many files, cannot upload"
|
||||
end
|
||||
current_site.increment_changed_count if results.include?(true)
|
||||
|
||||
results = current_site.store_files params[:files]
|
||||
file_upload_response
|
||||
end
|
||||
|
||||
post '/site_files/delete' do
|
||||
require_login
|
||||
current_site.delete_file 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
|
||||
|
||||
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.'
|
||||
end
|
||||
|
||||
current_site.store_file filename, tempfile
|
||||
current_site.store_files [{filename: filename, tempfile: tempfile}]
|
||||
|
||||
'ok'
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ get '/stats/?' do
|
|||
# expires 14400, :public, :must_revalidate if self.class.production? # 4 hours
|
||||
|
||||
@stats = {
|
||||
total_hosted_site_hits: DB['SELECT SUM(hits) FROM sites'].first[:sum],
|
||||
total_hosted_site_views: DB['SELECT SUM(views) FROM sites'].first[:sum],
|
||||
total_sites: Site.count,
|
||||
total_unbanned_sites: Site.where(is_banned: false).count,
|
||||
total_banned_sites: Site.where(is_banned: true).count,
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
def stripe_get_site_from_event(event)
|
||||
customer_id = event['data']['object']['customer']
|
||||
customer = Stripe::Customer.retrieve customer_id
|
||||
|
||||
# Some old accounts only have a username for the desc
|
||||
desc_split = customer.description.split(' - ')
|
||||
|
||||
if desc_split.length == 1
|
||||
site_where = {username: desc_split.first}
|
||||
end
|
||||
|
||||
if desc_split.last.to_i == 0
|
||||
site_where = {username: desc_split.first}
|
||||
else
|
||||
site_where = {id: desc_split.last}
|
||||
end
|
||||
|
||||
Site.where(site_where).first
|
||||
end
|
||||
|
||||
post '/stripe_webhook' do
|
||||
event = JSON.parse request.body.read
|
||||
if event['type'] == 'customer.created'
|
||||
|
@ -14,8 +34,7 @@ post '/stripe_webhook' do
|
|||
end
|
||||
|
||||
if event['type'] == 'charge.failed'
|
||||
site_id = event['data']['object']['description'].split(' - ').last
|
||||
site = Site[site_id]
|
||||
site = stripe_get_site_from_event event
|
||||
|
||||
EmailWorker.perform_async({
|
||||
from: 'web@neocities.org',
|
||||
|
@ -26,8 +45,7 @@ post '/stripe_webhook' do
|
|||
end
|
||||
|
||||
if event['type'] == 'customer.subscription.deleted'
|
||||
site_id = event['data']['object']['description'].split(' - ').last
|
||||
site = Site[site_id]
|
||||
site = stripe_get_site_from_event event
|
||||
site.stripe_subscription_id = nil
|
||||
site.plan_type = nil
|
||||
site.save_changes validate: false
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
get '/surf/?' do
|
||||
@current_page = params[:current_page].to_i || 1
|
||||
params.delete 'tag' if params[:tag].nil? || params[:tag].strip.empty?
|
||||
site_dataset = browse_sites_dataset
|
||||
site_dataset = site_dataset.paginate @current_page, 1
|
||||
@page_count = site_dataset.page_count || 1
|
||||
@site = site_dataset.first
|
||||
redirect "/browse?#{Rack::Utils.build_query params}" if @site.nil?
|
||||
@title = "Surf Mode - #{@site.title}"
|
||||
erb :'surf', layout: false
|
||||
end
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
def kickstarter_days_remaining
|
||||
ending = Time.parse('Sat, Jul 25 2015 3:05 PM PDT')
|
||||
today = Time.now
|
||||
|
||||
remaining = ending - today
|
||||
return 0 if remaining < 0
|
||||
|
||||
((ending - today) / 86400).to_i
|
||||
end
|
||||
|
||||
def dashboard_if_signed_in
|
||||
redirect '/dashboard' if signed_in?
|
||||
end
|
||||
|
||||
def require_login_ajax
|
||||
halt 'You are not logged in!' unless signed_in?
|
||||
halt 'You are banned.' if current_site.is_banned? || parent_site.is_banned?
|
||||
halt 'Please contact support.' if banned?
|
||||
end
|
||||
|
||||
def csrf_safe?
|
||||
|
@ -15,13 +25,13 @@ def csrf_token
|
|||
session[:_csrf_token] ||= SecureRandom.base64(32)
|
||||
end
|
||||
|
||||
def is_education?
|
||||
current_site && current_site.is_education
|
||||
end
|
||||
|
||||
def require_login
|
||||
redirect '/' unless signed_in?
|
||||
if session[:banned] || current_site.is_banned || parent_site.is_banned
|
||||
signout
|
||||
session[:banned] = true
|
||||
redirect '/'
|
||||
end
|
||||
enforce_ban if banned?
|
||||
end
|
||||
|
||||
def signed_in?
|
||||
|
@ -38,14 +48,18 @@ def parent_site
|
|||
current_site.parent? ? current_site : current_site.parent
|
||||
end
|
||||
|
||||
def require_unbanned_ip
|
||||
if session[:banned] || Site.banned_ip?(request.ip)
|
||||
signout
|
||||
session[:banned] = true
|
||||
flash[:error] = 'Site creation has been banned due to ToS violation/spam. '+
|
||||
'If you believe this to be in error, <a href="/contact">contact the site admin</a>.'
|
||||
return {result: 'error'}.to_json
|
||||
end
|
||||
def banned?(ip_check=false)
|
||||
return true if session[:banned]
|
||||
return true if current_site && (current_site.is_banned || parent_site.is_banned)
|
||||
|
||||
return true if ip_check && Site.banned_ip?(request.ip)
|
||||
false
|
||||
end
|
||||
|
||||
def enforce_ban
|
||||
signout
|
||||
session[:banned] = true
|
||||
redirect '/'
|
||||
end
|
||||
|
||||
def title
|
||||
|
@ -76,3 +90,31 @@ def send_confirmation_email(site=current_site)
|
|||
body: Tilt.new('./views/templates/email_confirm.erb', pretty: true).render(self, site: site)
|
||||
})
|
||||
end
|
||||
|
||||
def plan_pricing_button(plan_type)
|
||||
plan_type = plan_type.to_s
|
||||
|
||||
if !parent_site
|
||||
%{<a href="/#new" class="btn-Action">Sign Up</a>}
|
||||
elsif parent_site && parent_site.plan_type == plan_type
|
||||
if request.path.match /\/welcome/
|
||||
%{<a href="/" class="btn-Action">Get Started</a>}
|
||||
else
|
||||
%{<div class="current-plan">Current Plan</div>}
|
||||
end
|
||||
else
|
||||
#if plan_type == 'supporter'
|
||||
# plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]*12}, once per year"
|
||||
#else
|
||||
plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]}, monthly"
|
||||
#end
|
||||
|
||||
if request.path.match /\/welcome/
|
||||
button_title = 'Get Started'
|
||||
else
|
||||
button_title = parent_site.plan_type == 'free' ? 'Upgrade' : 'Change'
|
||||
end
|
||||
|
||||
%{<a data-plan_name="#{Site::PLAN_FEATURES[plan_type.to_sym][:name]}" data-plan_type="#{plan_type}" data-plan_price="#{plan_price}" onclick="card = new Skeuocard($('#skeuocard')); return false" class="btn-Action planPricingButton">#{button_title}</a>}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,4 +6,5 @@ recaptcha_private_key: '5678'
|
|||
phantomjs_url:
|
||||
- http://localhost:8910
|
||||
ip_hash_salt: "400$8$1$fc21863da5d531c1"
|
||||
email_unsubscribe_token: "somethingrandomderrrrp"
|
||||
email_unsubscribe_token: "somethingrandomderrrrp"
|
||||
logs_path: "/tmp/neocitiestestlogs"
|
||||
|
|
|
@ -61,6 +61,7 @@ end
|
|||
# :nocov:
|
||||
if ENV['RACK_ENV'] == 'development'
|
||||
# Run async jobs immediately in development.
|
||||
=begin
|
||||
module Sidekiq
|
||||
module Worker
|
||||
module ClassMethods
|
||||
|
@ -72,18 +73,7 @@ if ENV['RACK_ENV'] == 'development'
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# :nocov:
|
||||
if $config['pubsub_url']
|
||||
$pubsub_pool = ConnectionPool.new(size: 10, timeout: 5) {
|
||||
Redis.new url: $config['pubsub_url']
|
||||
}
|
||||
end
|
||||
|
||||
if $config['pubsub_url'].nil? && ENV['RACK_ENV'] == 'production'
|
||||
raise 'pubsub_url is missing from config'
|
||||
=end
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
|
@ -131,3 +121,11 @@ if ENV['RACK_ENV'] != 'development'
|
|||
# Sass::Plugin.options[:never_update] = true
|
||||
Sass::Plugin.options[:full_exception] = false
|
||||
end
|
||||
|
||||
require 'csv'
|
||||
|
||||
$country_codes = {}
|
||||
|
||||
CSV.foreach("./files/country_codes.csv") do |row|
|
||||
$country_codes[row.last] = row.first
|
||||
end
|
||||
|
|
|
@ -10,15 +10,28 @@ class Numeric
|
|||
end
|
||||
|
||||
def to_bytes_pretty
|
||||
space = (self.to_f / ONE_MEGABYTE).round(2)
|
||||
space = space.to_i if space.denominator == 1
|
||||
# if space >= 1000000
|
||||
# "#{space/1000000} TB"
|
||||
if space >= 1000
|
||||
"#{space/1000} GB"
|
||||
else
|
||||
"#{space} MB"
|
||||
end
|
||||
computed = nil
|
||||
unit = nil
|
||||
{
|
||||
'B' => 1000,
|
||||
'KB' => 1000 * 1000,
|
||||
'MB' => 1000 * 1000 * 1000,
|
||||
'GB' => 1000 * 1000 * 1000 * 1000,
|
||||
'TB' => 1000 * 1000 * 1000 * 1000 * 1000
|
||||
}.each_pair { |e, s|
|
||||
if self < s
|
||||
computed = (self.to_f / (s / 1000)).round(2)
|
||||
unit = e
|
||||
break
|
||||
end
|
||||
}
|
||||
computed = computed.to_i if computed.modulo(1) == 0.0
|
||||
|
||||
"#{computed} #{unit}"
|
||||
end
|
||||
|
||||
def to_comma_separated
|
||||
self.to_s.chars.to_a.reverse.each_slice(3).map(&:join).join(",").reverse
|
||||
end
|
||||
|
||||
def format_large_number
|
||||
|
@ -33,7 +46,7 @@ class Numeric
|
|||
unit_char = 'K' #thousand
|
||||
unit_amount = 1000.0
|
||||
end
|
||||
|
||||
|
||||
self_divided = self.to_f / unit_amount
|
||||
self_rounded = self_divided.round(1)
|
||||
|
||||
|
|
|
@ -11,4 +11,8 @@ class String
|
|||
def unindent
|
||||
gsub /^#{scan(/^\s*/).min_by{|l|l.length}}/, ""
|
||||
end
|
||||
|
||||
def is_integer?
|
||||
true if Integer(self) rescue false
|
||||
end
|
||||
end
|
||||
|
|
BIN
files/GeoLiteCity.dat
Normal file
BIN
files/GeoLiteCity.dat
Normal file
Binary file not shown.
250
files/country_codes.csv
Normal file
250
files/country_codes.csv
Normal file
|
@ -0,0 +1,250 @@
|
|||
Afghanistan,AF
|
||||
Åland Islands,AX
|
||||
Albania,AL
|
||||
Algeria,DZ
|
||||
American Samoa,AS
|
||||
Andorra,AD
|
||||
Angola,AO
|
||||
Anguilla,AI
|
||||
Antarctica,AQ
|
||||
Antigua and Barbuda,AG
|
||||
Argentina,AR
|
||||
Armenia,AM
|
||||
Aruba,AW
|
||||
Australia,AU
|
||||
Austria,AT
|
||||
Azerbaijan,AZ
|
||||
Bahamas,BS
|
||||
Bahrain,BH
|
||||
Bangladesh,BD
|
||||
Barbados,BB
|
||||
Belarus,BY
|
||||
Belgium,BE
|
||||
Belize,BZ
|
||||
Benin,BJ
|
||||
Bermuda,BM
|
||||
Bhutan,BT
|
||||
"Bolivia, Plurinational State of",BO
|
||||
"Bonaire, Sint Eustatius and Saba",BQ
|
||||
Bosnia and Herzegovina,BA
|
||||
Botswana,BW
|
||||
Bouvet Island,BV
|
||||
Brazil,BR
|
||||
British Indian Ocean Territory,IO
|
||||
Brunei Darussalam,BN
|
||||
Bulgaria,BG
|
||||
Burkina Faso,BF
|
||||
Burundi,BI
|
||||
Cambodia,KH
|
||||
Cameroon,CM
|
||||
Canada,CA
|
||||
Cape Verde,CV
|
||||
Cayman Islands,KY
|
||||
Central African Republic,CF
|
||||
Chad,TD
|
||||
Chile,CL
|
||||
China,CN
|
||||
Christmas Island,CX
|
||||
Cocos (Keeling) Islands,CC
|
||||
Colombia,CO
|
||||
Comoros,KM
|
||||
Congo,CG
|
||||
"Congo, the Democratic Republic of the",CD
|
||||
Cook Islands,CK
|
||||
Costa Rica,CR
|
||||
Côte d'Ivoire,CI
|
||||
Croatia,HR
|
||||
Cuba,CU
|
||||
Curaçao,CW
|
||||
Cyprus,CY
|
||||
Czech Republic,CZ
|
||||
Denmark,DK
|
||||
Djibouti,DJ
|
||||
Dominica,DM
|
||||
Dominican Republic,DO
|
||||
Ecuador,EC
|
||||
Egypt,EG
|
||||
El Salvador,SV
|
||||
Equatorial Guinea,GQ
|
||||
Eritrea,ER
|
||||
Estonia,EE
|
||||
Ethiopia,ET
|
||||
Falkland Islands (Malvinas),FK
|
||||
Faroe Islands,FO
|
||||
Fiji,FJ
|
||||
Finland,FI
|
||||
France,FR
|
||||
French Guiana,GF
|
||||
French Polynesia,PF
|
||||
French Southern Territories,TF
|
||||
Gabon,GA
|
||||
Gambia,GM
|
||||
Georgia,GE
|
||||
Germany,DE
|
||||
Ghana,GH
|
||||
Gibraltar,GI
|
||||
Greece,GR
|
||||
Greenland,GL
|
||||
Grenada,GD
|
||||
Guadeloupe,GP
|
||||
Guam,GU
|
||||
Guatemala,GT
|
||||
Guernsey,GG
|
||||
Guinea,GN
|
||||
Guinea-Bissau,GW
|
||||
Guyana,GY
|
||||
Haiti,HT
|
||||
Heard Island and McDonald Mcdonald Islands,HM
|
||||
Holy See (Vatican City State),VA
|
||||
Honduras,HN
|
||||
Hong Kong,HK
|
||||
Hungary,HU
|
||||
Iceland,IS
|
||||
India,IN
|
||||
Indonesia,ID
|
||||
"Iran, Islamic Republic of",IR
|
||||
Iraq,IQ
|
||||
Ireland,IE
|
||||
Isle of Man,IM
|
||||
Israel,IL
|
||||
Italy,IT
|
||||
Jamaica,JM
|
||||
Japan,JP
|
||||
Jersey,JE
|
||||
Jordan,JO
|
||||
Kazakhstan,KZ
|
||||
Kenya,KE
|
||||
Kiribati,KI
|
||||
"Korea, Democratic People's Republic of",KP
|
||||
"Korea, Republic of",KR
|
||||
Kuwait,KW
|
||||
Kyrgyzstan,KG
|
||||
Lao People's Democratic Republic,LA
|
||||
Latvia,LV
|
||||
Lebanon,LB
|
||||
Lesotho,LS
|
||||
Liberia,LR
|
||||
Libya,LY
|
||||
Liechtenstein,LI
|
||||
Lithuania,LT
|
||||
Luxembourg,LU
|
||||
Macao,MO
|
||||
"Macedonia, the Former Yugoslav Republic of",MK
|
||||
Madagascar,MG
|
||||
Malawi,MW
|
||||
Malaysia,MY
|
||||
Maldives,MV
|
||||
Mali,ML
|
||||
Malta,MT
|
||||
Marshall Islands,MH
|
||||
Martinique,MQ
|
||||
Mauritania,MR
|
||||
Mauritius,MU
|
||||
Mayotte,YT
|
||||
Mexico,MX
|
||||
"Micronesia, Federated States of",FM
|
||||
"Moldova, Republic of",MD
|
||||
Monaco,MC
|
||||
Mongolia,MN
|
||||
Montenegro,ME
|
||||
Montserrat,MS
|
||||
Morocco,MA
|
||||
Mozambique,MZ
|
||||
Myanmar,MM
|
||||
Namibia,NA
|
||||
Nauru,NR
|
||||
Nepal,NP
|
||||
Netherlands,NL
|
||||
New Caledonia,NC
|
||||
New Zealand,NZ
|
||||
Nicaragua,NI
|
||||
Niger,NE
|
||||
Nigeria,NG
|
||||
Niue,NU
|
||||
Norfolk Island,NF
|
||||
Northern Mariana Islands,MP
|
||||
Norway,NO
|
||||
Oman,OM
|
||||
Pakistan,PK
|
||||
Palau,PW
|
||||
"Palestine, State of",PS
|
||||
Panama,PA
|
||||
Papua New Guinea,PG
|
||||
Paraguay,PY
|
||||
Peru,PE
|
||||
Philippines,PH
|
||||
Pitcairn,PN
|
||||
Poland,PL
|
||||
Portugal,PT
|
||||
Puerto Rico,PR
|
||||
Qatar,QA
|
||||
Réunion,RE
|
||||
Romania,RO
|
||||
Russian Federation,RU
|
||||
Rwanda,RW
|
||||
Saint Barthélemy,BL
|
||||
"Saint Helena, Ascension and Tristan da Cunha",SH
|
||||
Saint Kitts and Nevis,KN
|
||||
Saint Lucia,LC
|
||||
Saint Martin (French part),MF
|
||||
Saint Pierre and Miquelon,PM
|
||||
Saint Vincent and the Grenadines,VC
|
||||
Samoa,WS
|
||||
San Marino,SM
|
||||
Sao Tome and Principe,ST
|
||||
Saudi Arabia,SA
|
||||
Senegal,SN
|
||||
Serbia,RS
|
||||
Seychelles,SC
|
||||
Sierra Leone,SL
|
||||
Singapore,SG
|
||||
Sint Maarten (Dutch part),SX
|
||||
Slovakia,SK
|
||||
Slovenia,SI
|
||||
Solomon Islands,SB
|
||||
Somalia,SO
|
||||
South Africa,ZA
|
||||
South Georgia and the South Sandwich Islands,GS
|
||||
South Sudan,SS
|
||||
Spain,ES
|
||||
Sri Lanka,LK
|
||||
Sudan,SD
|
||||
Suriname,SR
|
||||
Svalbard and Jan Mayen,SJ
|
||||
Swaziland,SZ
|
||||
Sweden,SE
|
||||
Switzerland,CH
|
||||
Syrian Arab Republic,SY
|
||||
"Taiwan, Province of China",TW
|
||||
Tajikistan,TJ
|
||||
"Tanzania, United Republic of",TZ
|
||||
Thailand,TH
|
||||
Timor-Leste,TL
|
||||
Togo,TG
|
||||
Tokelau,TK
|
||||
Tonga,TO
|
||||
Trinidad and Tobago,TT
|
||||
Tunisia,TN
|
||||
Turkey,TR
|
||||
Turkmenistan,TM
|
||||
Turks and Caicos Islands,TC
|
||||
Tuvalu,TV
|
||||
Uganda,UG
|
||||
Ukraine,UA
|
||||
United Arab Emirates,AE
|
||||
United Kingdom,GB
|
||||
United States,US
|
||||
United States Minor Outlying Islands,UM
|
||||
Uruguay,UY
|
||||
Uzbekistan,UZ
|
||||
Vanuatu,VU
|
||||
"Venezuela, Bolivarian Republic of",VE
|
||||
Viet Nam,VN
|
||||
"Virgin Islands, British",VG
|
||||
"Virgin Islands, U.S.",VI
|
||||
Wallis and Futuna,WF
|
||||
Western Sahara,EH
|
||||
Yemen,YE
|
||||
Zambia,ZM
|
||||
Zimbabwe,ZW
|
||||
European Union, EU
|
|
85
files/fullhitsmigration.rb
Normal file
85
files/fullhitsmigration.rb
Normal file
|
@ -0,0 +1,85 @@
|
|||
raise 'nope'
|
||||
|
||||
Sequel.migration do
|
||||
up {
|
||||
raise 'derp'
|
||||
DB.drop_table :stats
|
||||
DB.drop_table :stat_referrers
|
||||
DB.drop_table :stat_paths
|
||||
DB.drop_table :stat_locations
|
||||
|
||||
DB.create_table! :hits do
|
||||
primary_key :id
|
||||
Integer :site_id, index: true
|
||||
Integer :hit_referrer_id
|
||||
Integer :hit_path_id
|
||||
Integer :hit_location_id
|
||||
Bignum :bandwidth
|
||||
Time :accessed_at, index: true
|
||||
end
|
||||
|
||||
DB.create_table! :hit_referrers do
|
||||
primary_key :id
|
||||
String :uri, index: {unique: true}
|
||||
end
|
||||
|
||||
DB.create_table! :hit_locations do
|
||||
primary_key :id
|
||||
String :country_code2
|
||||
String :region_name
|
||||
String :city_name
|
||||
Float :latitude
|
||||
Float :longitude
|
||||
end
|
||||
|
||||
DB.create_table! :hit_paths do
|
||||
primary_key :id
|
||||
String :path, index: {unique: true}
|
||||
end
|
||||
}
|
||||
|
||||
down {
|
||||
raise 'No.' if ENV['RACK_ENV'] == 'production'
|
||||
|
||||
%i{hits hit_referrers hit_locations hit_paths}.each do |t|
|
||||
DB.drop_table t
|
||||
end
|
||||
|
||||
DB.create_table! :stats do
|
||||
primary_key :id
|
||||
Integer :site_id
|
||||
Date :created_at
|
||||
Integer :hits
|
||||
Integer :views
|
||||
Integer :comments
|
||||
Integer :follows
|
||||
Integer :site_updates
|
||||
end
|
||||
|
||||
DB.create_table! :stat_referrers do
|
||||
primary_key :id
|
||||
Integer :stat_id
|
||||
String :url
|
||||
String :views
|
||||
end
|
||||
|
||||
DB.create_table! :stat_locations do
|
||||
primary_key :id
|
||||
Integer :stat_id
|
||||
String :country_code2
|
||||
String :region_name
|
||||
String :city_name
|
||||
Decimal :latitude
|
||||
Decimal :longitude
|
||||
Integer :views
|
||||
end
|
||||
|
||||
DB.create_table :stat_paths do
|
||||
primary_key :id
|
||||
Integer :stat_id
|
||||
String :name
|
||||
Integer :views
|
||||
end
|
||||
}
|
||||
end
|
||||
|
11
migrations/057_add_paypal_data.rb
Normal file
11
migrations/057_add_paypal_data.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :paypal_profile_id, String
|
||||
DB.add_column :sites, :paypal_token, String
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :paypal_profile_id
|
||||
DB.drop_column :sites, :paypal_token
|
||||
}
|
||||
end
|
9
migrations/058_add_paypal_status.rb
Normal file
9
migrations/058_add_paypal_status.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :paypal_active, :boolean, default: false
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :paypal_active
|
||||
}
|
||||
end
|
55
migrations/059_refactor_stats.rb
Normal file
55
migrations/059_refactor_stats.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.drop_table :stats
|
||||
DB.create_table! :stats do
|
||||
primary_key :id
|
||||
Integer :site_id, index: true
|
||||
Date :created_at, index: true
|
||||
Integer :hits, default: 0
|
||||
Integer :views, default: 0
|
||||
Integer :comments, default: 0
|
||||
Integer :follows, default: 0
|
||||
Integer :site_updates, default: 0
|
||||
end
|
||||
|
||||
DB.create_table! :stat_referrers do
|
||||
primary_key :id
|
||||
Integer :stat_id, index: true
|
||||
String :url
|
||||
Integer :views, default: 0
|
||||
end
|
||||
|
||||
DB.create_table! :stat_locations do
|
||||
primary_key :id
|
||||
Integer :stat_id, index: true
|
||||
String :country_code2
|
||||
String :region_name
|
||||
String :city_name
|
||||
Decimal :latitude
|
||||
Decimal :longitude
|
||||
Integer :views, default: 0
|
||||
end
|
||||
|
||||
DB.create_table! :stat_paths do
|
||||
primary_key :id
|
||||
Integer :stat_id, index: true
|
||||
String :name
|
||||
Integer :views, default: 0
|
||||
end
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_table :stats
|
||||
DB.create_table! :stats do
|
||||
primary_key :id
|
||||
Integer :site_id, index: true
|
||||
Integer :hits, default: 0
|
||||
Integer :views, default: 0
|
||||
DateTime :created_at, index: true
|
||||
end
|
||||
|
||||
DB.drop_table :stat_referrers
|
||||
DB.drop_table :stat_locations
|
||||
DB.drop_table :stat_paths
|
||||
}
|
||||
end
|
18
migrations/060_separate_stat_timestamps.rb
Normal file
18
migrations/060_separate_stat_timestamps.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# This migration detaches stat_referrers, stat_locations and stat_paths
|
||||
# from stats. Instead of stat_id, we'll add a created_at timestamp and remove
|
||||
# after 7 days for both free and supporter plans (for now).
|
||||
Sequel.migration do
|
||||
up {
|
||||
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||
drop_column stat_table, :stat_id
|
||||
add_column stat_table, :created_at, :date, index: true
|
||||
end
|
||||
}
|
||||
|
||||
down {
|
||||
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||
drop_column stat_table, :created_at
|
||||
add_column stat_table, :stat_id, :integer, index: true
|
||||
end
|
||||
}
|
||||
end
|
16
migrations/061_add_site_ids.rb
Normal file
16
migrations/061_add_site_ids.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# This migration detaches stat_referrers, stat_locations and stat_paths
|
||||
# from stats. Instead of stat_id, we'll add a created_at timestamp and remove
|
||||
# after 7 days for both free and supporter plans (for now).
|
||||
Sequel.migration do
|
||||
up {
|
||||
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||
add_column stat_table, :site_id, :integer, index: true
|
||||
end
|
||||
}
|
||||
|
||||
down {
|
||||
[:stat_referrers, :stat_paths, :stat_locations].each do |stat_table|
|
||||
drop_column stat_table, :site_id
|
||||
end
|
||||
}
|
||||
end
|
12
migrations/062_fix_latlng.rb
Normal file
12
migrations/062_fix_latlng.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
drop_column :stat_locations, :latitude
|
||||
drop_column :stat_locations, :longitude
|
||||
add_column :stat_locations, :latitude, :float
|
||||
add_column :stat_locations, :longitude, :float
|
||||
}
|
||||
|
||||
down {
|
||||
# meh.
|
||||
}
|
||||
end
|
9
migrations/063_add_bandwidth_to_stats.rb
Normal file
9
migrations/063_add_bandwidth_to_stats.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
add_column :stats, :bandwidth, :bigint, default: 0
|
||||
}
|
||||
|
||||
down {
|
||||
drop_column :stats, :bandwidth
|
||||
}
|
||||
end
|
9
migrations/064_add_education_to_sites.rb
Normal file
9
migrations/064_add_education_to_sites.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
add_column :sites, :is_education, :boolean, default: false
|
||||
}
|
||||
|
||||
down {
|
||||
drop_column :sites, :is_education
|
||||
}
|
||||
end
|
14
migrations/065_add_ipfs.rb
Normal file
14
migrations/065_add_ipfs.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.create_table! :archives do
|
||||
Integer :site_id, index: true
|
||||
String :ipfs_hash
|
||||
DateTime :updated_at, index: true
|
||||
unique [:site_id, :ipfs_hash]
|
||||
end
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_table :archives
|
||||
}
|
||||
end
|
9
migrations/066_add_username_index_to_sites.rb
Normal file
9
migrations/066_add_username_index_to_sites.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_index :sites, :username
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_index :sites, :username
|
||||
}
|
||||
end
|
13
migrations/067_add_missing_stat_indexes.rb
Normal file
13
migrations/067_add_missing_stat_indexes.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB['create index stat_referrers_hash_multi on stat_referrers (site_id, md5(url))'].first
|
||||
DB.add_index :stat_locations, :site_id
|
||||
DB.add_index :stat_paths, :site_id
|
||||
}
|
||||
|
||||
down {
|
||||
DB['drop index stat_referrers_hash_multi'].first
|
||||
DB.drop_index :stat_locations, :site_id
|
||||
DB.drop_index :stat_paths, :site_id
|
||||
}
|
||||
end
|
9
migrations/068_add_stat_referrer_site_id_index.rb
Normal file
9
migrations/068_add_stat_referrer_site_id_index.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_index :stat_referrers, :site_id
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_index :stat_referrers, :site_id
|
||||
}
|
||||
end
|
13
migrations/069_add_stat_created_indexes.rb
Normal file
13
migrations/069_add_stat_created_indexes.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
%i{stat_referrers stat_locations stat_paths}.each do |t|
|
||||
DB.add_index t, :created_at
|
||||
end
|
||||
}
|
||||
|
||||
down {
|
||||
%i{stat_referrers stat_locations stat_paths}.each do |t|
|
||||
DB.drop_index t, :created_at
|
||||
end
|
||||
}
|
||||
end
|
9
models/archive.rb
Normal file
9
models/archive.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class Archive < Sequel::Model
|
||||
many_to_one :site
|
||||
set_primary_key [:site_id, :ipfs_hash]
|
||||
unrestrict_primary_key
|
||||
|
||||
def url
|
||||
"https://#{ipfs_hash}.ipfs.neocities.org"
|
||||
end
|
||||
end
|
370
models/site.rb
370
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
|
||||
}
|
||||
|
||||
VALID_EDITABLE_EXTENSIONS = %w{
|
||||
html htm txt js css md manifest
|
||||
}
|
||||
|
||||
MINIMUM_PASSWORD_LENGTH = 5
|
||||
BAD_USERNAME_REGEX = /[^\w-]/i
|
||||
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
|
||||
SPAM_MATCH_REGEX = ENV['RACK_ENV'] == 'test' ? /pillz/ : /#{$config['spam_smart_filter'].join('|')}/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
|
||||
TITLE_MAX = 100
|
||||
|
||||
|
@ -98,20 +102,35 @@ class Site < Sequel::Model
|
|||
unlimited_site_creation: true,
|
||||
custom_ssl_certificates: true,
|
||||
no_file_restrictions: true,
|
||||
custom_domains: true
|
||||
custom_domains: true,
|
||||
maximum_site_files: 25000
|
||||
}
|
||||
|
||||
PLAN_FEATURES[:free] = PLAN_FEATURES[:supporter].merge(
|
||||
name: 'Free',
|
||||
space: Filesize.from('50MB').to_i,
|
||||
space: Filesize.from('100MB').to_i,
|
||||
bandwidth: Filesize.from('50GB').to_i,
|
||||
price: 0,
|
||||
unlimited_site_creation: false,
|
||||
custom_ssl_certificates: 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)
|
||||
PLAN_FEATURES[plan_type.to_sym][key.to_sym]
|
||||
end
|
||||
|
@ -128,7 +147,15 @@ class Site < Sequel::Model
|
|||
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
|
||||
|
||||
|
@ -161,6 +188,13 @@ class Site < Sequel::Model
|
|||
|
||||
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
|
||||
Site.where(Sequel.|({id: owner.id}, {parent_site_id: owner.id})).order(:parent_site_id.desc, :username)
|
||||
end
|
||||
|
@ -260,6 +294,7 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
def banned_ip?(ip)
|
||||
return false if ENV['RACK_ENV'] == 'production' && ip == '127.0.0.1'
|
||||
return true if Site.where(is_banned: true).
|
||||
where(ip: hash_ip(ip)).
|
||||
where(['updated_at > ?', Time.now-BANNED_TIME]).
|
||||
|
@ -360,25 +395,26 @@ class Site < Sequel::Model
|
|||
def install_new_files
|
||||
FileUtils.mkdir_p files_path
|
||||
|
||||
files = []
|
||||
|
||||
%w{index not_found}.each do |name|
|
||||
tmpfile = Tempfile.new "newinstall-#{name}"
|
||||
tmpfile.write render_template("#{name}.erb")
|
||||
tmpfile.close
|
||||
|
||||
store_file "#{name}.html", tmpfile, new_install: true
|
||||
purge_cache "/#{name}.html"
|
||||
ScreenshotWorker.perform_async values[:username], "#{name}.html"
|
||||
files << {filename: "#{name}.html", tempfile: tmpfile}
|
||||
end
|
||||
|
||||
tmpfile = Tempfile.new 'style.css'
|
||||
tmpfile.close
|
||||
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.close
|
||||
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
|
||||
|
||||
def get_file(path)
|
||||
|
@ -539,77 +575,57 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
def purge_cache(path)
|
||||
relative_path = path.gsub(base_files_path, '')
|
||||
payload = {site: username, path: relative_path}
|
||||
payload[:domain] = domain if !domain.empty?
|
||||
PurgeCacheWorker.perform_async payload
|
||||
relative_path = path.gsub base_files_path, ''
|
||||
|
||||
# We gotta flush the dirname too if it's an index file.
|
||||
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
|
||||
|
||||
def store_file(path, uploaded, opts={})
|
||||
relative_path = scrubbed_path path
|
||||
path = files_path path
|
||||
pathname = Pathname(path)
|
||||
Rye::Cmd.add_command :ipfs, nil, 'add', :r
|
||||
|
||||
site_file = site_files_dataset.where(path: relative_path).first
|
||||
|
||||
uploaded_sha1 = Digest::SHA1.file(uploaded.path).hexdigest
|
||||
|
||||
if site_file && site_file.sha1_hash == uploaded_sha1
|
||||
return false
|
||||
end
|
||||
|
||||
if relative_path == 'index.html' && opts[:new_install] != true
|
||||
def add_to_ipfs
|
||||
# Not ideal. An SoA version is in progress.
|
||||
if $config['ipfs_ssh_host'] && $config['ipfs_ssh_user']
|
||||
rbox = Rye::Box.new $config['ipfs_ssh_host'], :user => $config['ipfs_ssh_user']
|
||||
begin
|
||||
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
|
||||
end
|
||||
response = rbox.ipfs "sites/#{self.username.gsub(/\/|\.\./, '')}"
|
||||
output_array = response
|
||||
ensure
|
||||
rbox.disconnect
|
||||
end
|
||||
|
||||
self.site_changed = true
|
||||
self.site_updated_at = Time.now
|
||||
self.updated_at = Time.now
|
||||
|
||||
save_changes(validate: false)
|
||||
else
|
||||
line = Cocaine::CommandLine.new('ipfs', 'add -r :path')
|
||||
response = line.run path: files_path
|
||||
output_array = response.to_s.split("\n")
|
||||
end
|
||||
|
||||
if pathname.extname.match HTML_REGEX
|
||||
# SPAM and phishing checking code goes here
|
||||
output_array.last.split(' ')[1]
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
def latest_archive
|
||||
@latest_archive ||= archives_dataset.order(:updated_at.desc).first
|
||||
end
|
||||
|
||||
def is_directory?(path)
|
||||
|
@ -626,11 +642,6 @@ class Site < Sequel::Model
|
|||
true
|
||||
end
|
||||
|
||||
def increment_changed_count
|
||||
self.changed_count += 1
|
||||
save_changes(validate: false)
|
||||
end
|
||||
|
||||
def files_zip
|
||||
zip_name = "neocities-#{username}"
|
||||
|
||||
|
@ -654,41 +665,17 @@ class Site < Sequel::Model
|
|||
tmpfile.path
|
||||
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)
|
||||
FileUtils.mv base_files_path(oldusername), base_files_path
|
||||
end
|
||||
|
||||
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
|
||||
tmpfile.unlink
|
||||
end
|
||||
|
||||
def file_exists?(path)
|
||||
|
@ -830,6 +817,18 @@ class Site < Sequel::Model
|
|||
new_tags.compact!
|
||||
@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)
|
||||
errors.add :new_tags_string, 'Cannot have more than 5 tags for your site.'
|
||||
end
|
||||
|
@ -856,7 +855,7 @@ class Site < Sequel::Model
|
|||
break
|
||||
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.uniq!
|
||||
|
@ -906,9 +905,17 @@ class Site < Sequel::Model
|
|||
is_root_index: file_path == "#{base_files_path}/index.html"
|
||||
}
|
||||
|
||||
site_file = site_files_dataset.where(path: file_path.gsub(base_files_path, '').sub(/^\//, '')).first
|
||||
|
||||
if site_file
|
||||
file[:size] = site_file.size unless file[:is_directory]
|
||||
file[:updated_at] = site_file.updated_at
|
||||
end
|
||||
|
||||
file[:is_html] = !(file[:ext].match HTML_REGEX).nil?
|
||||
file[:is_image] = !(file[:ext].match IMAGE_REGEX).nil?
|
||||
file[:is_editable] = !(file[:ext].match EDITABLE_FILE_EXT).nil?
|
||||
|
||||
file
|
||||
end
|
||||
|
||||
|
@ -945,6 +952,7 @@ class Site < Sequel::Model
|
|||
((total_space_used.to_f / maximum_space) * 100).round(1)
|
||||
end
|
||||
|
||||
# Note: Change Stat#prune! if you change this business logic.
|
||||
def supporter?
|
||||
owner.plan_type != 'free'
|
||||
end
|
||||
|
@ -966,6 +974,7 @@ class Site < Sequel::Model
|
|||
!values[:plan_type].match(/plan_/).nil?
|
||||
end
|
||||
|
||||
# Note: Change Stat#prune! if you change this business logic.
|
||||
def plan_type
|
||||
return 'free' if owner.values[:plan_type].nil?
|
||||
return 'supporter' if owner.values[:plan_type].match /^plan_/
|
||||
|
@ -1089,4 +1098,149 @@ class Site < Sequel::Model
|
|||
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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
class SiteFile < Sequel::Model
|
||||
|
||||
unrestrict_primary_key
|
||||
plugin :update_primary_key
|
||||
many_to_one :site
|
||||
end
|
||||
end
|
||||
|
|
243
models/stat.rb
243
models/stat.rb
|
@ -1,3 +1,244 @@
|
|||
class Stat < Sequel::Model
|
||||
FREE_RETAINMENT_DAYS = 30
|
||||
|
||||
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 comment = form.find('[name="comment"]').val()
|
||||
form.remove()
|
||||
|
||||
$.post('/event/'+eventId+'/comment', {csrf_token: csrfToken, message: comment}, function(res) {
|
||||
console.log(res)
|
||||
location.reload()
|
||||
})
|
||||
},
|
||||
|
||||
delete: function(commentId, csrfToken) {
|
||||
$.post('/comment/'+commentId+'/delete', {csrf_token: csrfToken}, function(res) {
|
||||
console.log(res)
|
||||
location.reload()
|
||||
})
|
||||
},
|
||||
|
|
|
@ -59,7 +59,7 @@ a{
|
|||
@import 'project-Main'; // Project Specific Main Content Area Styling
|
||||
@import 'project-Footer'; // Project Specific Footer Styling
|
||||
|
||||
.alert{
|
||||
.alert-error {
|
||||
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{
|
||||
@extend %kill-List;
|
||||
|
@ -490,4 +507,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
.interior .constant-Nav{margin:0}
|
||||
.interior .constant-Nav, .hp.education .constant-Nav{margin:0}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@
|
|||
float:left;
|
||||
margin-bottom:$spacing*2;
|
||||
color: #666;
|
||||
@include box-shadow(1px 1px 2px 0px rgba(0, 0, 0, 0.18));
|
||||
@include box-shadow(0px 1px 3px 0px rgba(0, 0, 0, 0.18));
|
||||
|
||||
@media (max-device-width:480px), screen and (max-width:800px){
|
||||
width:50%
|
||||
|
|
|
@ -51,4 +51,4 @@ describe '/admin' do
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,10 +17,22 @@ describe 'dashboard' do
|
|||
visit '/dashboard'
|
||||
click_link 'New Folder'
|
||||
fill_in 'name', with: 'testimages'
|
||||
click_button 'Create'
|
||||
#click_button 'Create'
|
||||
all('#createDir button[type=submit]').first.click
|
||||
page.must_have_content /testimages/
|
||||
File.directory?(@site.files_path('testimages')).must_equal true
|
||||
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
|
||||
|
|
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
|
||||
Capybara.reset_sessions!
|
||||
end
|
||||
end
|
||||
|
||||
Capybara.default_wait_time = 5
|
||||
|
|
|
@ -11,8 +11,8 @@ describe '/' do
|
|||
|
||||
it 'loads the news feed with welcome' do
|
||||
visit '/'
|
||||
page.body.must_match /Neocities news feed/i
|
||||
page.body.must_match /You aren’t following any websites yet/i
|
||||
page.body.must_match /Thanks for joining the Neocities community/i
|
||||
page.body.wont_match /You aren’t following any websites yet/i
|
||||
end
|
||||
|
||||
it 'displays a follow and an unrelated follow' do
|
||||
|
|
|
@ -30,16 +30,19 @@ describe 'signup' do
|
|||
Capybara.default_driver = :poltergeist
|
||||
Capybara.reset_sessions!
|
||||
visit_signup
|
||||
page.must_have_content 'Neocities' # Used to force load wait
|
||||
end
|
||||
|
||||
after do
|
||||
Capybara.default_driver = :rack_test
|
||||
BlockedIp.where(ip: '127.0.0.1').delete
|
||||
DB[:sites].where(is_banned: true).delete
|
||||
end
|
||||
|
||||
it 'succeeds with valid data' do
|
||||
fill_in_valid
|
||||
click_signup_button
|
||||
site_created?.must_equal true
|
||||
site_created?
|
||||
|
||||
index_file_path = File.join Site::SITE_FILES_ROOT, @site[:username], 'index.html'
|
||||
File.exist?(index_file_path).must_equal true
|
||||
|
@ -48,19 +51,36 @@ describe 'signup' do
|
|||
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 false
|
||||
|
||||
site.ip.must_equal Site.hash_ip('127.0.0.1')
|
||||
end
|
||||
|
||||
it 'fails to create for existing site' do
|
||||
it 'fails if site with same ip has been banned' do
|
||||
@banned_site = Fabricate :site
|
||||
@banned_site.is_banned = true
|
||||
@banned_site.save_changes
|
||||
|
||||
fill_in_valid
|
||||
click_signup_button
|
||||
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]
|
||||
Site[username: @site[:username]].must_be_nil
|
||||
current_path.must_equal '/'
|
||||
page.wont_have_content 'Welcome to Neocities'
|
||||
end
|
||||
|
||||
it 'fails if IP is banned from blocked ips list' do
|
||||
DB[:blocked_ips].insert(ip: '127.0.0.1', created_at: Time.now)
|
||||
fill_in_valid
|
||||
click_signup_button
|
||||
Site[username: @site[:username]].must_be_nil
|
||||
current_path.must_equal '/'
|
||||
page.wont_have_content 'Welcome to Neocities'
|
||||
end
|
||||
|
||||
it 'fails to create for existing site' do
|
||||
@existing_site = Fabricate :site
|
||||
fill_in_valid
|
||||
fill_in 'username', with: @existing_site.username
|
||||
click_signup_button
|
||||
page.must_have_content 'already taken'
|
||||
end
|
||||
|
@ -113,9 +133,6 @@ describe 'signup' do
|
|||
page.must_have_content /email.+exists/
|
||||
end
|
||||
|
||||
puts "$$$$$$$$$$$$$$$$$$$$$$ TODO FIX TAGS TESTS"
|
||||
|
||||
=begin
|
||||
it 'succeeds with no tags' do
|
||||
fill_in_valid
|
||||
fill_in 'new_tags_string', with: ''
|
||||
|
@ -139,7 +156,7 @@ puts "$$$$$$$$$$$$$$$$$$$$$$ TODO FIX TAGS TESTS"
|
|||
Site.last.tags.collect {|t| t.name}.must_equal ['derpie', 'shoujo']
|
||||
end
|
||||
|
||||
it 'fails with invalid tag chars' do
|
||||
it 'fails with invalid tag chars' do
|
||||
fill_in_valid
|
||||
fill_in 'new_tags_string', with: '$POLICE OFFICER$$$$$, derp'
|
||||
click_signup_button
|
||||
|
@ -179,9 +196,10 @@ puts "$$$$$$$$$$$$$$$$$$$$$$ TODO FIX TAGS TESTS"
|
|||
fill_in 'new_tags_string', with: 'one, one'
|
||||
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.first.name.must_equal 'one'
|
||||
end
|
||||
=end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ describe 'site page' do
|
|||
click_button 'Post'
|
||||
@site.profile_comments.count.must_equal 1
|
||||
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!'
|
||||
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
|
||||
create_site
|
||||
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']
|
||||
res[:result].must_equal 'success'
|
||||
|
||||
|
@ -102,16 +102,37 @@ describe 'api delete' do
|
|||
it 'fails with missing files' do
|
||||
create_site
|
||||
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']
|
||||
res[:error_type].must_equal 'missing_files'
|
||||
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
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
@site.store_file 'test.jpg', 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: 'test.jpg', tempfile: 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']
|
||||
res[:result].must_equal 'success'
|
||||
site_file_exists?('test.jpg').must_equal false
|
||||
|
@ -139,6 +160,19 @@ describe 'api upload' do
|
|||
res[:error_type].must_equal 'missing_files'
|
||||
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
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
|
@ -167,14 +201,6 @@ describe 'api upload' do
|
|||
'/' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
res[:error_type].must_equal 'invalid_file_type'
|
||||
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
|
||||
res[:error_type].must_equal 'missing_files'
|
||||
end
|
||||
|
||||
it 'fails for file with no extension' do
|
||||
|
|
|
@ -50,4 +50,4 @@ I18n.enforce_available_locales = true
|
|||
|
||||
Mail.defaults do
|
||||
delivery_method :test
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,21 +18,25 @@ describe 'site_files' do
|
|||
before do
|
||||
@site = Fabricate :site
|
||||
ThumbnailWorker.jobs.clear
|
||||
PurgeCacheOrderWorker.jobs.clear
|
||||
PurgeCacheWorker.jobs.clear
|
||||
ScreenshotWorker.jobs.clear
|
||||
end
|
||||
|
||||
describe 'delete' 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.exists?(file_path).must_equal true
|
||||
delete_file filename: 'test.jpg'
|
||||
File.exists?(file_path).must_equal false
|
||||
SiteFile[site_id: @site.id, path: 'test.jpg'].must_be_nil
|
||||
@site.reload.space_used.must_equal 0
|
||||
end
|
||||
|
||||
it 'deletes all files in a directory' do
|
||||
it 'deletes a directory and all files in it' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'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 1
|
||||
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
|
||||
|
||||
describe 'upload' do
|
||||
|
@ -82,10 +101,31 @@ describe 'site_files' do
|
|||
args = ScreenshotWorker.jobs.first['args']
|
||||
args.first.must_equal @site.username
|
||||
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.site_changed.must_equal true
|
||||
@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
|
||||
|
||||
it 'does not change title for subdir index.html' do
|
||||
|
@ -97,15 +137,19 @@ describe 'site_files' do
|
|||
@site.reload.title.must_equal title
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
File.exists?(@site.files_path('test.jpg')).must_equal true
|
||||
|
||||
queue_args = PurgeCacheWorker.jobs.first['args'].first
|
||||
queue_args['site'].must_equal @site.username
|
||||
queue_args['path'].must_equal '/test.jpg'
|
||||
username, path = PurgeCacheOrderWorker.jobs.first['args']
|
||||
username.must_equal @site.username
|
||||
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.drain
|
||||
|
@ -114,7 +158,7 @@ describe 'site_files' do
|
|||
File.exists?(@site.thumbnail_path('test.jpg', resolution)).must_equal true
|
||||
end
|
||||
|
||||
@site.site_changed.must_equal false
|
||||
@site.site_changed.must_equal true
|
||||
end
|
||||
|
||||
it 'fails with unsupported file' do
|
||||
|
@ -153,9 +197,18 @@ describe 'site_files' do
|
|||
last_response.body.must_match /successfully uploaded/i
|
||||
File.exists?(@site.files_path('derpie/derptest/test.jpg')).must_equal true
|
||||
|
||||
PurgeCacheWorker.jobs.length.must_equal 1
|
||||
queue_args = PurgeCacheWorker.jobs.first['args'].first
|
||||
queue_args['path'].must_equal '/derpie/derptest/test.jpg'
|
||||
PurgeCacheOrderWorker.jobs.length.must_equal 1
|
||||
username, path = PurgeCacheOrderWorker.jobs.first['args']
|
||||
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.drain
|
||||
|
|
|
@ -53,10 +53,12 @@ describe Site do
|
|||
end
|
||||
|
||||
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.plan_type.must_equal plan_type
|
||||
end
|
||||
site = Fabricate :site, plan_type: nil
|
||||
site.plan_type.must_equal 'free'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -77,4 +79,4 @@ describe Site do
|
|||
site.suggestions.length.must_equal Site::SUGGESTIONS_LIMIT
|
||||
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">
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/donate">Donate</a></li>
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
<li><a href="/api">API</a></li>
|
||||
<li><a href="/press">Press</a></li>
|
||||
<% unless is_education? %>
|
||||
<li><a href="/blog">Blog</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="/privacy" rel="nofollow">Privacy</a></li>
|
||||
<li><a href="/contact" rel="nofollow">Contact</a></li>
|
||||
|
|
|
@ -30,17 +30,19 @@
|
|||
</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
|
||||
<li>
|
||||
<a href="/?activity=mine"><span class="float-Left">Activity</span>
|
||||
<% if current_site.unseen_notifications_count > 0 %>
|
||||
<span class="notification-value"><%= current_site.unseen_notifications_count %></span>
|
||||
<% end %>
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<% unless is_education? %>
|
||||
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
|
||||
<li>
|
||||
<a href="/?activity=mine"><span class="float-Left">Activity</span>
|
||||
<% if current_site.unseen_notifications_count > 0 %>
|
||||
<span class="notification-value"><%= current_site.unseen_notifications_count %></span>
|
||||
<% end %>
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<% end %>
|
||||
<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><a href="/settings">Settings</a></li>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<% if request.path == '/' && !signed_in? %>
|
||||
<% if (request.path == '/' && !signed_in?) || request.path == '/education' %>
|
||||
<li>
|
||||
<a href="/">Neocities</a>
|
||||
</li>
|
||||
|
@ -6,12 +6,16 @@
|
|||
<li>
|
||||
<a href="/browse">Websites</a>
|
||||
</li>
|
||||
<% unless is_education? %>
|
||||
<li>
|
||||
<a href="/activity">Activity</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<li>
|
||||
<a href="/tutorials">Learn</a>
|
||||
</li>
|
||||
<% unless is_education? %>
|
||||
<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>
|
||||
<% 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">
|
||||
<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...">
|
||||
<button class="btn-Action">Post</button>
|
||||
</form>
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
|
||||
<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 %>
|
||||
<div class="alert alert-error alert-block">
|
||||
<% flash.keys.each do |key| %>
|
||||
|
|
49
views/admin/email.erb
Normal file
49
views/admin/email.erb
Normal file
|
@ -0,0 +1,49 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>Email Mass Send</h1>
|
||||
<h2 class="subtitle">Use wisely.</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content misc-page single-Col txt-Center" style="padding-top: 20px;">
|
||||
|
||||
<% if flash.keys.length > 0 %>
|
||||
<div class="alert alert-error alert-block">
|
||||
<% flash.keys.each do |key| %>
|
||||
<%== flash[key] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-100">
|
||||
<p>
|
||||
This sends to all emails on Neocities that have not opted out. It trickles it out, 4000/day to prevent spam report issues.
|
||||
</p>
|
||||
|
||||
<form method="POST" action="/admin/email">
|
||||
<%== csrf_token_input_html %>
|
||||
<p>
|
||||
Subject:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input name="subject" type="text" style="width: 600px">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Body:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<textarea name="body" style="width: 600px" rows="10"></textarea>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input type="submit" value="Send" class="btn">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
45
views/admin/reports.erb
Normal file
45
views/admin/reports.erb
Normal file
|
@ -0,0 +1,45 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>Site Reports</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content" style="background: white">
|
||||
<form method="POST" action="/admin/report">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<th>Type</th>
|
||||
<th>Comments</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
<% @reports.each do |report| %>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<%= report.site.uri %>"><%= report.site.title %></a>
|
||||
<br>
|
||||
<img src="<%= report.site.screenshot_url('/index.html', '540x405') %>">
|
||||
<br>
|
||||
Reported <%= report.created_at.ago %>
|
||||
<% if report.reporting_site %>
|
||||
by <a href="<%= report.reporting_site.uri %>"><%= report.reporting_site.username %></a>
|
||||
<% end %>
|
||||
</td>
|
||||
<td><%= report.type %></td>
|
||||
<td><%= report.comments[0...100] %></td>
|
||||
<td>
|
||||
<select name="sites[<%= report.site_id %>]">
|
||||
<option value="">No Action</option>
|
||||
<option value="ban">Ban Site</option>
|
||||
<option value="nsfw">Mark NSFW</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="Perform Actions" class="btn">
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
|
@ -12,40 +12,48 @@
|
|||
<div class="row content">
|
||||
<div class="col col-100">
|
||||
<% if params[:tag] %>
|
||||
<h1>Sites tagged <%= params[:tag] %></h1>
|
||||
<h1><a href="/browse">Websites</a> > <%= params[:tag] %></h1>
|
||||
<% else %>
|
||||
<h1>Sites on Neocities</h1>
|
||||
<h1>Websites on Neocities</h1>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<form id="search_criteria" action="/browse" method="GET">
|
||||
<div class="col col-50 filter">
|
||||
<fieldset class="grouping">
|
||||
<label class="text-Label" for="sort_by">Sort by:</label>
|
||||
<div class="select-Container">
|
||||
<select name="sort_by" id="sort_by" class="input-Select">
|
||||
<option value="last_updated" <%= params[:sort_by] == 'last_updated' ? 'selected' : '' %>>Last Updated</option>
|
||||
<option value="views" <%= params[:sort_by] == 'views' ? 'selected' : '' %>>Most Views</option>
|
||||
<option value="hits" <%= params[:sort_by] == 'hits' ? 'selected' : '' %>>Most Hits</option>
|
||||
<option value="newest" <%= params[:sort_by] == 'newest' ? 'selected' : '' %>>Newest</option>
|
||||
<option value="oldest" <%= params[:sort_by] == 'oldest' ? 'selected' : '' %>>Oldest</option>
|
||||
<option value="random" <%= params[:sort_by] == 'random' ? 'selected' : '' %>>Random</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--
|
||||
<div>
|
||||
<input name="is_nsfw" type="checkbox" value="true" <%= params[:is_nsfw].to_s == 'true' ? 'checked' : '' %>> Show 18+ content
|
||||
</div>
|
||||
-->
|
||||
<% unless is_education? %>
|
||||
<label class="text-Label" for="sort_by">Sort by:</label>
|
||||
<div class="select-Container">
|
||||
<select name="sort_by" id="sort_by" class="input-Select">
|
||||
<option value="followers" <%= params[:sort_by] == 'followers' ? 'selected' : '' %>>Most Followed</option>
|
||||
<option value="last_updated" <%= params[:sort_by] == 'last_updated' ? 'selected' : '' %>>Last Updated</option>
|
||||
<option value="supporters" <%= params[:sort_by] == 'supporters' ? 'selected' : '' %>>Neocities Supporters</option>
|
||||
<option value="featured" <%= params[:sort_by] == 'featured' ? 'selected' : '' %>>Featured</option>
|
||||
<option value="views" <%= params[:sort_by] == 'views' ? 'selected' : '' %>>Most Views</option>
|
||||
<option value="hits" <%= params[:sort_by] == 'hits' ? 'selected' : '' %>>Most Hits</option>
|
||||
<option value="newest" <%= params[:sort_by] == 'newest' ? 'selected' : '' %>>Newest</option>
|
||||
<option value="oldest" <%= params[:sort_by] == 'oldest' ? 'selected' : '' %>>Oldest</option>
|
||||
<option value="random" <%= params[:sort_by] == 'random' ? 'selected' : '' %>>Random</option>
|
||||
</select>
|
||||
</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">
|
||||
<% end %>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col col-50 filter">
|
||||
<form method="GET" action="browse">
|
||||
<fieldset class="grouping">
|
||||
<label class="text-Label" for="tag"><span class="hide-on-mobile">Search by </span>Tag:</label>
|
||||
<input class="input-Area typeahead" id="tag" name="tag" type="text" placeholder="pokemon" value="<%= params[:tag] %>" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" dir="auto">
|
||||
<input style="vertical-align: -4px;margin-left: 4px;" type="submit" class="btn-Action" value="Search">
|
||||
<% unless is_education? || params[:sort_by] == 'followers' %>
|
||||
<label class="text-Label" for="tag"><span class="hide-on-mobile">Filter by </span>Tag:</label>
|
||||
<input class="input-Area typeahead" id="tag" name="tag" type="text" placeholder="pokemon" value="<%= params[:tag] %>" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" dir="auto">
|
||||
<input style="vertical-align: -4px;margin-left: 4px;" type="submit" class="btn-Action" value="Filter">
|
||||
<% end %>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -141,12 +149,35 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class="row website-Gallery content int-Gall tags">
|
||||
<h3>Popular Tags</h3>
|
||||
<p>
|
||||
<% Tag.popular_names(100).each do |tag| %>
|
||||
<a href="/browse?tag=<%= Rack::Utils.escape tag[:name] %>"><%= tag[:name] %></a>
|
||||
<% end %>
|
||||
</p>
|
||||
<% unless is_education? %>
|
||||
<div class="row content misc">
|
||||
<h3>Popular Tags</h3>
|
||||
<p>
|
||||
<% Tag.popular_names(100).each do |tag| %>
|
||||
<a href="/browse?tag=<%= Rack::Utils.escape tag[:name] %>"><%= tag[:name] %></a>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row content misc">
|
||||
<h3>Text Search</h3>
|
||||
<form id="searchForm" method="GET" action="https://duckduckgo.com" class="content" onsubmit="return addSiteToSearch()">
|
||||
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||
<fieldset>
|
||||
<input id="searchQuery" name="q" type="text" placeholder="keywords" class="input-Area" autocapitalize="off" autocorrect="off" value="<%= flash[:username] %>" style="width: 50%">
|
||||
<input class="btn btn-Action" type="submit" value="Search">
|
||||
</fieldset>
|
||||
</form>
|
||||
<p>Search powered by <a href="https://duckduckgo.com/">Duck Duck Go</a></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function addSiteToSearch() {
|
||||
var searchQuery = $('#searchQuery')
|
||||
var finalSearchQuery = searchQuery.val() + ' site:neocities.org'
|
||||
|
||||
window.location = 'https://duckduckgo.com/?q='+encodeURI(finalSearchQuery)
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<h2>Contact</h2>
|
||||
|
||||
<p>
|
||||
Please note that we can only respond to messages sent in the English language.
|
||||
Please note that we can only respond to english inquiries.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
<div class="header-Outro with-site-image dashboard">
|
||||
<div class="row content wide">
|
||||
|
||||
<div class="col col-50 signup-Area">
|
||||
<div class="signup-Form">
|
||||
<fieldset class="content">
|
||||
|
@ -30,7 +29,7 @@
|
|||
<div class="col col-50">
|
||||
<h2 class="eps"><%= current_site.title %></h2>
|
||||
<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>
|
||||
</p>
|
||||
<ul>
|
||||
|
@ -39,7 +38,7 @@
|
|||
<% 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>.
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -51,7 +50,7 @@
|
|||
|
||||
<div class="content wide">
|
||||
|
||||
<% unless current_site.changed_count > 5 %>
|
||||
<% unless current_site.changed_count >= 1 %>
|
||||
<div class="welcome">
|
||||
<!-- <div class="close-button"></div> -->
|
||||
<h4>Hello! Welcome to your new site.</h4>
|
||||
|
@ -75,7 +74,11 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="files">
|
||||
<div id="filesDisplay" class="files">
|
||||
<script>
|
||||
if(localStorage && localStorage.getItem('viewType') == 'list')
|
||||
$('#filesDisplay').addClass('list-view')
|
||||
</script>
|
||||
<div id="uploadingOverlay" class="uploading-overlay" style="display: none">
|
||||
<div class="uploading">
|
||||
<p>Uploading, please wait...</p>
|
||||
|
@ -83,6 +86,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-default iconview-button" title="Icon View" onclick="iconView()"><i class="fa fa-th"></i></button>
|
||||
<button type="button" class="btn btn-default listview-button" title="List View" onclick="listView()"><i class="fa fa-list"></i></button>
|
||||
</div>
|
||||
<div class="breadcrumbs">
|
||||
<% if params[:dir].nil? || params[:dir].empty? || params[:dir] == '/' %>
|
||||
Home
|
||||
|
@ -102,7 +109,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
<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="#" class="btn-Action" onclick="clickUploadFiles(); return false"><i class="fa fa-arrow-circle-up"></i> Upload</a>
|
||||
</div>
|
||||
|
@ -115,6 +122,7 @@
|
|||
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
||||
<% @file_list.each do |file| %>
|
||||
<div class="file filehover">
|
||||
<!-- <input type="checkbox" name="" value="" /> -->
|
||||
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
|
||||
<div class="html-thumbnail html fileimagehover">
|
||||
<img src="<%= current_site.screenshot_url(file[:path], '210x158') %>" alt="">
|
||||
|
@ -138,12 +146,19 @@
|
|||
<% end %>
|
||||
|
||||
<a class="title">
|
||||
<% if file[:name].length > 19 %>
|
||||
<%= file[:name].slice(0..18) %>…
|
||||
<% else %>
|
||||
<%= file[:name] %>
|
||||
<% end %>
|
||||
<%= file[:name] %>
|
||||
</a>
|
||||
<div class="column size">
|
||||
<% if file[:size] %>
|
||||
<%= file[:size].to_bytes_pretty %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="column updated">
|
||||
<% if file[:updated_at] %>
|
||||
<%= file[:updated_at].ago %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="overlay">
|
||||
<% if file[:is_editable] %>
|
||||
<a href="/site_files/text_editor<%= file[:path] %>"><i class="fa fa-edit" title="Edit"></i> Edit</a>
|
||||
|
@ -189,8 +204,10 @@
|
|||
<% if !current_site.plan_feature(:no_file_restrictions) %>
|
||||
<a href="/site_files/allowed_types">Allowed file types</a> |
|
||||
<% end %>
|
||||
<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>
|
||||
<a href="/site_files/<%= current_site.username %>.zip">Download entire site</a>
|
||||
<% unless is_education? %>
|
||||
| <a href="/site_files/mount_info">Mount your site as a drive on your computer!</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -203,6 +220,44 @@
|
|||
<input id="uploadFiles" type="file" name="files[]" multiple onchange="$('#uploadFilesButtonForm').submit()">
|
||||
</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>
|
||||
|
||||
|
@ -246,8 +301,12 @@
|
|||
|
||||
this.on("error", function(file, errorMessage) {
|
||||
hideUploadProgress()
|
||||
location.href = '/dashboard<%= @dir ? "?dir=#{@dir}" : "" %>'
|
||||
// alert('Failed: '+errorMessage)
|
||||
// Guess a directory upload error
|
||||
if(file.status == 'error' && file.name.match(/.+\..+/) == null && errorMessage == 'Server responded with 0 code.') {
|
||||
alert('Recursive directory upload is only supported by the Chrome web browser.')
|
||||
} else {
|
||||
location.href = '/dashboard<%= @dir ? "?dir=#{@dir}" : "" %>'
|
||||
}
|
||||
})
|
||||
|
||||
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
|
||||
|
@ -258,24 +317,32 @@
|
|||
$('#progressBar').css('display', 'block')
|
||||
$('#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">
|
||||
<form method="post" action="/site/create_directory">
|
||||
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
|
||||
<input type="hidden" value="<%= @dir %>" name="dir">
|
||||
<div class="modal-header">
|
||||
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
|
||||
<h3 id="createDirLabel">Create Folder</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input name="name" type="text" placeholder="folder_name">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
<button type="submit" class="btn-Action">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
$('#createDir').on('shown', function () {
|
||||
$('#newDirInput').focus();
|
||||
})
|
||||
|
||||
$('#createFile').on('shown', function () {
|
||||
$('#newFileInput').focus();
|
||||
})
|
||||
|
||||
function listView() {
|
||||
if(localStorage)
|
||||
localStorage.setItem('viewType', 'list')
|
||||
|
||||
$('#filesDisplay').addClass('list-view')
|
||||
}
|
||||
|
||||
function iconView() {
|
||||
if(localStorage)
|
||||
localStorage.removeItem('viewType')
|
||||
|
||||
$('#filesDisplay').removeClass('list-view')
|
||||
}
|
||||
</script>
|
||||
|
|
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="col-left">
|
||||
<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">
|
||||
<h4>Welcome to your Neocities news feed!</h4>
|
||||
<p>
|
||||
|
@ -32,14 +43,6 @@
|
|||
</div>
|
||||
<% 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? %>
|
||||
<%== erb :'_news', layout: false, locals: {site: current_site, events: @events} %>
|
||||
<% end %>
|
||||
|
@ -87,7 +90,7 @@
|
|||
|
||||
<div class="col col-33">
|
||||
<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="col col-50">
|
||||
<% if site.updated_at %>
|
||||
|
@ -104,7 +107,7 @@
|
|||
</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">
|
||||
<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>
|
||||
<!--[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" />
|
||||
<body class="hp">
|
||||
<a id="new"></a>
|
||||
|
||||
<title>Neocities: Create your free website now!</title>
|
||||
<meta itemprop="name" content="Neocities.org" />
|
||||
<meta itemprop="description" content="Create your own free home page, and do whatever you want with it." />
|
||||
<meta name="description" content="Neocities is the new Geocities. Create your own free home page, and do whatever you want with it." />
|
||||
<meta name="keywords" content="free website, html, css, learn to code, free hosting, build a website, create a web page" />
|
||||
<div class="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"/>
|
||||
<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."/>
|
||||
<ul class="status-Nav">
|
||||
|
||||
<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" />
|
||||
<% 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>
|
||||
|
||||
|
||||
<!-- Mobile Meta -->
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
|
||||
|
||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script type="text/javascript" src="/js/html5.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script src="/js/jquery-1.11.0.min.js"></script>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
</head>
|
||||
|
||||
<body class="hp">
|
||||
<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 %>
|
||||
<% 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>
|
||||
<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.org</span>
|
||||
<img src="/img/neocities-Logo.png" alt="Neocities.org" />
|
||||
</h1>
|
||||
<h1 class="logo header-Content content">
|
||||
<span class="hidden">Neocities.org</span>
|
||||
<img src="/img/neocities-Logo.png" alt="Neocities.org" />
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<div class="header-Outro">
|
||||
<div class="row header-Content content">
|
||||
<div class="col intro">
|
||||
<h2 class="section-header">Create your own free web site, and discover new ones.</h2>
|
||||
<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!
|
||||
</p>
|
||||
<ul class="intro-List">
|
||||
<li class="intro-Social">
|
||||
<span class="intro-Icon"></span>
|
||||
<h3 class="delta">Share your web creation with the world</h3>
|
||||
<p class="base">
|
||||
Follow your favorite Neocities sites to keep up with all their latest updates. Discover new websites related to your interests using tags, comment on them, and share them. Unlimited creativity, zero ads.
|
||||
</p>
|
||||
</li>
|
||||
<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>
|
||||
|
||||
<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 class="row header-Content content">
|
||||
<div class="col intro">
|
||||
<h2 class="section-header">Create your own free web site. Unlimited creativity, zero ads.</h2>
|
||||
<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!
|
||||
</p>
|
||||
<div class="intro-List kickstarter">
|
||||
<div class="col col-40">
|
||||
<a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev"><img src="/img/kickstarterthumbnail.jpg" style="width: 100%; float: left; margin-right: 20px; border: 3px solid white;margin-bottom: 1em"></a>
|
||||
</div>
|
||||
<div class="col col-60">
|
||||
<div class="title">
|
||||
<a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">
|
||||
SUPPORT OUR
|
||||
<img src="/img/kickstarterlogo.png" style="width: 280px; display:block; margin: 11px 0px;">
|
||||
<%= kickstarter_days_remaining %> DAYS LEFT
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<form id="createSiteForm" class="signup-Form" onsubmit="return false">
|
||||
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
|
||||
<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>
|
||||
<p>We need your help to build an amazing HTML/CSS course into Neocities! <a href="https://www.kickstarter.com/projects/1262953102/neocities-30-an-interactive-html-css-course-for-ev">Learn More >></a></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--<ul class="intro-List">
|
||||
<li class="intro-Social">
|
||||
<span class="intro-Icon"></span>
|
||||
<h3 class="delta">Share your web creation with the world</h3>
|
||||
<p class="base">
|
||||
Follow your favorite Neocities sites to keep up with all their latest updates. Discover new websites related to your interests using tags, comment on them, and share them.
|
||||
</p>
|
||||
</li>
|
||||
<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>
|
||||
<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" />
|
||||
<div class="col signup-Area">
|
||||
<% if signed_in? %>
|
||||
|
||||
<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="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="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="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>
|
||||
<label for="tags-input">Tags (your interests, site topics)</label>
|
||||
<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" />
|
||||
|
||||
<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" 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">
|
||||
<div style="margin-top: 15px">
|
||||
<input type="submit" value="Create My Site" class="btn-Action float-Right" />
|
||||
</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>
|
||||
</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 -->
|
||||
|
||||
|
@ -206,118 +178,118 @@
|
|||
<main class="content-Base">
|
||||
|
||||
<div class="section featured-Websites">
|
||||
<h2 class="delta">Featured Sites</h2>
|
||||
<!--
|
||||
<div class="nav prev"></div>
|
||||
-->
|
||||
<ul class="website-Gallery hp-Gallery">
|
||||
<% Site.featured.each do |site| %>
|
||||
<li>
|
||||
<a href="<%= site.uri %>" title="<%= site.title %>" target="_blank">
|
||||
<img src="<%= site.screenshot_url 'index.html', '210x158' %>" class="neo-SS" alt="<%= site.title %>" />
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<!--
|
||||
<div class="nav next"></div>
|
||||
-->
|
||||
<a href="/browse" class="btn-Action float-Right">Browse all sites</a>
|
||||
<h2 class="delta">Featured Sites</h2>
|
||||
<!--
|
||||
<div class="nav prev"></div>
|
||||
-->
|
||||
<ul class="website-Gallery hp-Gallery">
|
||||
<% Site.featured.each do |site| %>
|
||||
<li>
|
||||
<a href="<%= site.uri %>" title="<%= site.title %>" target="_blank">
|
||||
<img src="<%= site.screenshot_url 'index.html', '210x158' %>" class="neo-SS" alt="<%= site.title %>" />
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<!--
|
||||
<div class="nav next"></div>
|
||||
-->
|
||||
<a href="/browse" class="btn-Action float-Right">Browse all sites</a>
|
||||
</div>
|
||||
|
||||
<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="col col-50"><div class="screenshot" style="background-image:url(/img/front-editor-screenshot.png)"></div></div>
|
||||
<div class="col col-50 text">
|
||||
<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>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>
|
||||
</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 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 text">
|
||||
<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>If you'd rather use your favorite desktop editor, no problem. Uploading files is as easy as drag-n-drop.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="section features">
|
||||
<div class="row">
|
||||
<div class="col col-50">
|
||||
<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>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>
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<% # erb :'plan/_pricing' %>
|
||||
|
||||
<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>
|
||||
"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>
|
||||
<section class="section bottom-signup">
|
||||
<h2>What are you waiting for? <a href="#new">Start building your web site now!</a></h2>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer-Base" role="contentinfo">
|
||||
|
@ -358,47 +330,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%== erb :'_footer', layout: false %>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<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) {
|
||||
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>
|
||||
</div>
|
||||
<%== erb :'_index_signup_script', layout: false %>
|
||||
</body>
|
||||
|
|
44
views/index_layout.erb
Normal file
44
views/index_layout.erb
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!doctype html>
|
||||
<!--[if IE 8 ]><html lang="en" class="ieAll ie8"><![endif]-->
|
||||
<!--[if IE 9 ]><html lang="en" class="ieAll ie9"><![endif]-->
|
||||
<!--[if (gt IE 9)|!(IE)]><!--><html lang="en"><!--<![endif]-->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
||||
<title>Neocities: Create your free website now!</title>
|
||||
<meta itemprop="name" content="Neocities" />
|
||||
<meta itemprop="description" content="Create your own free web site, and do whatever you want with it." />
|
||||
<meta name="description" content="Create your own free web site, and do whatever you want with it." />
|
||||
<meta name="keywords" content="free website, html, css, learn to code, free hosting, build a website, create a web page" />
|
||||
|
||||
<link rel="canonical" href="//neocities.org" />
|
||||
|
||||
<meta property="og:title" content="Neocities"/>
|
||||
<meta property="og:site_name" content="Neocities | neocities.org"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:image" content=""/>
|
||||
<meta property="og:url" content="//www.neocities.org"/>
|
||||
<meta property="og:description" content="Create your own free web site, and do whatever you want with it."/>
|
||||
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico?v=4" />
|
||||
<link rel="apple-touch-icon-precomposed" href="#apple-icon-144.png" />
|
||||
<link rel="apple-touch-startup-image" href="#startup.png" />
|
||||
|
||||
<!-- Mobile Meta -->
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
|
||||
|
||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script type="text/javascript" src="/js/html5.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script src="/js/jquery-1.11.0.min.js"></script>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
</head>
|
||||
|
||||
<%== yield %>
|
||||
|
||||
</html>
|
35
views/permanent_web.erb
Normal file
35
views/permanent_web.erb
Normal file
|
@ -0,0 +1,35 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>Neocities and the Permanent Web</h1>
|
||||
<h2 class="subtitle">Working to build a faster, better, more permanent web.</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content single-Col misc-page">
|
||||
<img src="/img/neocities-ipfs.jpg" style="margin-bottom: 20px">
|
||||
<article role="article">
|
||||
<p>
|
||||
Neocities has launched an experimental implementation of <a href="http://ipfs.io">IPFS</a>. IPFS is short for the "Inter-Planetary File System", and is the foundation for a new way to distribute web content that is being called The Permanent Web. The idea behind the Permanent Web is simple: Instead of serving web sites from central servers, we believe that web serving should be decentralized, and that IPFS is an eventual replacement to the aging HTTP protocol.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is still very early stage technology, so if you don't understand what this stuff is, don't worry about it for now. But if you'd like to read more about why we're interested in this new technology, please see our <a href="#">blog post</a> on the topic.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
IPFS archiving and downloading is now supported by <strong>all web sites</strong> on Neocities. You'll see an IPFS hash link on the site profile, and an archive link that allows you to see past versions of your site (note: this is still a preview, so past site archives may still disappear, but we're working on making it better).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you want to play around with this new technology, you can get IPFS for your computer and use it to retreive content from our IPFS node servers. All you need to do is <a href="http://ipfs.io/docs/install/">download the IPFS daemon</a> (OSX/Linux only for now), and run the following command in your terminal:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code>$ ipfs pin add -r THE_IPFS_HASH_FOR_YOUR_SITE</code>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We have a lot of very interesting projects we're working on with IPFS that will make Neocities even better. Stay tuned for some interesting new technology over the next year!
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
152
views/plan/_compare.erb
Normal file
152
views/plan/_compare.erb
Normal file
|
@ -0,0 +1,152 @@
|
|||
<h2 style="margin-top: 2em">What You Get</h2>
|
||||
<table class="plan-chart">
|
||||
<tr>
|
||||
<th class="feature-column"></th>
|
||||
<th>
|
||||
<h3>Free</h3>
|
||||
<% if request.path != '/welcome' %>
|
||||
<p>$<%= Site::PLAN_FEATURES[:free][:price] %>/mo</p>
|
||||
<%== plan_pricing_button :free %>
|
||||
<% end %>
|
||||
</th>
|
||||
<th class="professional">
|
||||
<h3>Supporter</h3>
|
||||
<% if request.path != '/welcome' %>
|
||||
<% if parent_site && parent_site.legacy_supporter? %>
|
||||
<p>$<%= Site::LEGACY_SUPPORTER_PRICES[parent_site.values[:plan_type].to_sym] %>/mo</p>
|
||||
<div class="current-plan">Current Plan</div>
|
||||
<% else %>
|
||||
<p>$<%= Site::PLAN_FEATURES[:supporter][:price] %>/mo</p>
|
||||
<%== plan_pricing_button :supporter %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="How much disk space you can use for your site. More space means you can upload more files.">Storage</span>
|
||||
</td>
|
||||
<td><%= Site::PLAN_FEATURES[:free][:space].to_bytes_pretty %></td>
|
||||
<td><%= (Site::PLAN_FEATURES[:supporter][:space] / (10**6)).to_comma_separated %> MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="How much content you can serve in one month. Don’t worry—these are soft limits. Temporary surges are fine, we won't take your site down automatically, and we're very flexible.">Bandwidth</span>
|
||||
</td>
|
||||
<td><%= Site::PLAN_FEATURES[:free][:bandwidth].to_bytes_pretty %></td>
|
||||
<td><%= Site::PLAN_FEATURES[:supporter][:bandwidth].to_bytes_pretty %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="We will never put ads on your site, or give your data to marketers. In fact, we’re prohibited from doing it in our own Terms of Service.">No advertising, ever</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Our worldwide network of CDN servers cache your web site regionally, making your site load fast—no matter where your users are. 頑張る, 日本!">Global CDN Site Cache</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Each site comes with a free Neocities address (yoursitename.neocities.org).">Free Site Subdomain</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Downloading your neocities site is as easy as clicking a button.">One-Click Backups</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Site tags make it easy to find sites you’re interested in, and join communities of sites. It’s like a web ring, but even better.">Site Tags</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Each site has its own RSS feed that make it easy to let people know when your site changes.">RSS Feed</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Like a site on Neocities? Now you can follow it, and see when they update!">Follow Your Favorite Sites</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Each site now has its own profile page, allowing users to see when your site updates, follow your site, and leave feedback.">Site Profile</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Our HTML5 site editor makes it easy to make quick changes to your site, even if you’re on the run.">Neocities Site Editor</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td class="feature-column">Site Tipping (Coming Soon)</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
-->
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="You can mount your Neocities site to your computer as a hard drive, making it easy to update with your favorite HTML editor.">WebDAV Uploading</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Add your domain name (yoursite.com) to your site!">Custom Domains</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Make as many sites as you want with one account, and easily switch between them.">Unlimited Site Creation</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Upload your SSL certificate for your custom domain name, preventing snoops from seeing your user’s traffic.">Custom SSL Certs (soon)</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="No whitelists—you can upload anything you want (no copyrighted content, trojans, or w4r3z, please).">No File Upload Type Restrictions</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$('.feature-column span').tooltip({
|
||||
placement: 'right'
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -1,31 +1,5 @@
|
|||
<%
|
||||
def plan_pricing_button(plan_type)
|
||||
plan_type = plan_type.to_s
|
||||
|
||||
if !parent_site
|
||||
%{<a href="/#new" class="btn-Action">Sign Up</a>}
|
||||
elsif parent_site && parent_site.plan_type == plan_type
|
||||
if request.path.match /\/welcome/
|
||||
%{<a href="/" class="btn-Action">Get Started</a>}
|
||||
else
|
||||
%{<div class="current-plan">Current Plan</div>}
|
||||
end
|
||||
else
|
||||
#if plan_type == 'supporter'
|
||||
# plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]*12}, once per year"
|
||||
#else
|
||||
plan_price = "$#{Site::PLAN_FEATURES[plan_type.to_sym][:price]}, monthly"
|
||||
#end
|
||||
|
||||
if request.path.match /\/welcome/
|
||||
button_title = 'Get Started'
|
||||
else
|
||||
button_title = parent_site.plan_type == 'free' ? 'Upgrade' : 'Change'
|
||||
end
|
||||
|
||||
%{<a data-plan_name="#{Site::PLAN_FEATURES[plan_type.to_sym][:name]}" data-plan_type="#{plan_type}" data-plan_price="#{plan_price}" onclick="card = new Skeuocard($('#skeuocard')); return false" class="btn-Action planPricingButton">#{button_title}</a>}
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<section class="section plans">
|
||||
|
@ -125,147 +99,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <h2><a href="">Compare Plans <i class="fa fa-caret-down"></i></a></h2> -->
|
||||
<h2 style="margin-top: 2em">Compare</h2>
|
||||
<table class="plan-chart">
|
||||
<tr>
|
||||
<th class="feature-column"></th>
|
||||
<th>
|
||||
<h3>Free</h3>
|
||||
<p>$<%= Site::PLAN_FEATURES[:free][:price] %>/mo</p>
|
||||
<%== plan_pricing_button :free %>
|
||||
</th>
|
||||
<th class="professional">
|
||||
<h3>Supporter</h3>
|
||||
<% if parent_site && parent_site.legacy_supporter? %>
|
||||
<p>$<%= Site::LEGACY_SUPPORTER_PRICES[parent_site.values[:plan_type].to_sym] %>/mo</p>
|
||||
<div class="current-plan">Current Plan</div>
|
||||
<% else %>
|
||||
<p>$<%= Site::PLAN_FEATURES[:supporter][:price] %>/mo</p>
|
||||
<%== plan_pricing_button :supporter %>
|
||||
<% end %>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="How much disk space you can use for your site. More space means you can upload more files.">Storage</span>
|
||||
</td>
|
||||
<td><%= Site::PLAN_FEATURES[:free][:space].to_bytes_pretty %></td>
|
||||
<td><%= Site::PLAN_FEATURES[:supporter][:space].to_bytes_pretty %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="How much content you can serve in one month. Don’t worry—these are soft limits. Temporary surges are fine, we won't take your site down automatically, and we're very flexible.">Bandwidth</span>
|
||||
</td>
|
||||
<td><%= Site::PLAN_FEATURES[:free][:bandwidth].to_bytes_pretty %></td>
|
||||
<td>1000 GB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="We will never put ads on your site, or give your data to marketers. In fact, we’re prohibited from doing it in our own Terms of Service.">No advertising, ever</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Our worldwide network of CDN servers cache your web site regionally, making your site load fast—no matter where your users are. 頑張る, 日本!">Global CDN Site Cache</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Each site comes with a free Neocities address (yoursitename.neocities.org).">Free Site Subdomain</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Downloading your neocities site is as easy as clicking a button.">One-Click Backups</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Site tags make it easy to find sites you’re interested in, and join communities of sites. It’s like a web ring, but even better.">Site Tags</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Each site has its own RSS feed that make it easy to let people know when your site changes.">RSS Feed</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Like a site on Neocities? Now you can follow it, and see when they update!">Follow Your Favorite Sites</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Each site now has its own profile page, allowing users to see when your site updates, follow your site, and leave feedback.">Site Profile</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Our HTML5 site editor makes it easy to make quick changes to your site, even if you’re on the run.">Neocities Site Editor</span>
|
||||
</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td class="feature-column">Site Tipping (Coming Soon)</td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
-->
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="You can mount your Neocities site to your computer as a hard drive, making it easy to update with your favorite HTML editor.">WebDAV Uploading</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Add your domain name (yoursite.com) to your site!">Custom Domains</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Make as many sites as you want with one account, and easily switch between them.">Unlimited Site Creation</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="Upload your SSL certificate for your custom domain name, preventing snoops from seeing your user’s traffic.">Custom SSL Certs (soon)</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature-column">
|
||||
<span data-original-title="No whitelists—you can upload anything you want (no copyrighted content, trojans, or w4r3z, please).">No File Upload Type Restrictions</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><i class="fa fa-check"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
<%== erb :'plan/_compare', layout: false %>
|
||||
|
||||
<% if request.path.match /\/plan/ %>
|
||||
<div class="row" style="margin-top: 50px; margin-bottom: 0px;">
|
||||
|
@ -300,7 +134,7 @@
|
|||
<strong>It's safe.</strong> We use <a href="https://stripe.com" target="_blank">Stripe</a> for payments, and never store your credit card information directly.
|
||||
</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>
|
||||
<strong>You can cancel or change plans anytime.</strong> If you do, we'll refund or credit the amount you didn't use.
|
||||
|
@ -313,10 +147,6 @@
|
|||
|
||||
<script>
|
||||
$(function() {
|
||||
$('.feature-column span').tooltip({
|
||||
placement: 'right'
|
||||
})
|
||||
|
||||
$('.plan-image').tooltip({
|
||||
placement: 'top'
|
||||
})
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
<p>For people that can't use a Credit Card, we support PayPal for supporter upgrades.</p>
|
||||
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
<input type="hidden" name="hosted_button_id" value="RVN4HCVY4FT9Y">
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribeCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
<input type="hidden" name="hosted_button_id" value="DFQBR5LNS3NW8">
|
||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribeCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
||||
</form>
|
||||
|
||||
<h2>The Bitcoin Lifer Plan</h2>
|
||||
|
|
|
@ -14,6 +14,16 @@
|
|||
<div class="col col-66" style="min-height: 43em;">
|
||||
<h2>Latest News</h2>
|
||||
<div class="press-news">
|
||||
<h3><a href="http://recode.net/2015/07/17/why-we-all-need-to-make-the-internet-fun-again/"><strong>Re/code</strong>:
|
||||
Why We All Need to Make the Internet Fun Again</a></h3>
|
||||
<blockquote>
|
||||
In a word, the Internet has become boring. When it went mass market in the mid-’90s, the Web was promised as a place of open exploration and creativity. Now, instead, it restricts our activity at nearly every turn. This doesn’t just constrain us as people, but threatens to impede the very inventiveness that the Internet industry depends on to continue thriving. What’s needed now is an understanding of how we reached this point — and an alternative vision for the Internet’s next generation.
|
||||
</blockquote>
|
||||
<br>
|
||||
<blockquote>
|
||||
Fortunately, we are starting to see a strong movement away from the templated, uniform Internet. Instead of defining the limits of their identity and expressiveness through social media, [people] have already turned en masse to indie games like Minecraft (bought by Microsoft for $2.5 billion last year), a free-form, online sandbox world, with building tools that enable them to build everything from massive 3-D cities to working computers and continent-spanning roller coasters. [Neocities], a quasi-rebirth of GeoCities and a vanguard member of the independent Web movement, has seen enormous growth since launching in 2013, with nearly 50,000 websites created by its users.
|
||||
</blockquote>
|
||||
|
||||
<h3><a href="http://www.royalgazette.com/article/20150103/NEWS/150109957"><strong>The Royal Gazette</strong>: Students learn to be junior web wizards</a></h3>
|
||||
<blockquote>
|
||||
About 60 students attended the Island’s first Hackathon yesterday to learn about computer coding and building their own websites. One of the Hackathon organisers, James Tucker, said: “Some of the students have been able to take what they learned here and move that from the Codecademy site and into a real webpage, so they have now got a presence on the web, which is their own thing that they produced themselves.” The budding programmers were able to get their partial websites online for free, using the free web-hosting system Neocities.
|
||||
|
|
|
@ -75,4 +75,4 @@ $(document).ready(function() {
|
|||
return location.hash = $(e.target).attr('href').substr(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</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>
|
||||
|
||||
<% if current_site.custom_domain_available? %>
|
||||
|
@ -17,7 +17,7 @@
|
|||
|
||||
<h3>Step 1</h3>
|
||||
<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><code>54.68.34.66</code></p>
|
||||
|
@ -81,4 +81,4 @@
|
|||
</form>
|
||||
<% end %>
|
||||
|
||||
-->
|
||||
-->
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
<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>
|
||||
<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="stat"><strong><%= site.views.format_large_number %></strong> <span>view<%= site.views == 1 ? '' : 's' %></span></div>
|
||||
<% 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>
|
||||
<% 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 %>
|
||||
<% is_following = current_site.is_following?(site) %>
|
||||
|
||||
|
@ -109,6 +120,7 @@
|
|||
</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>
|
||||
|
||||
<%== erb :'_follows', layout: false, locals: {site: site, is_current_site: site == current_site} %>
|
||||
|
|
30
views/site/archives.erb
Normal file
30
views/site/archives.erb
Normal file
|
@ -0,0 +1,30 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>Permanent Web Archives</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content single-Col misc-page">
|
||||
<article role="article">
|
||||
<% if @archives.length == 0 %>
|
||||
No archives yet.
|
||||
<% else %>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>IPFS Hash <small style="display: inline"><a href="/permanent-web">(what is this?)</a></small></th>
|
||||
<th>Archived Time</th>
|
||||
</tr>
|
||||
<% @archives.each do |archive| %>
|
||||
<tr>
|
||||
<td><a href="<%= archive.url %>"><%= archive.ipfs_hash %></a></td>
|
||||
<td><%= archive.updated_at.ago.downcase %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Note: This is a very early preview release of a new technology! We're still figuring things out. We may stop hosting archives without notice. <a href="/permanent-web">Learn how you can host your own copies of these archives</a>.
|
||||
</p>
|
||||
<% end %>
|
||||
</article>
|
||||
</div>
|
342
views/site/stats.erb
Normal file
342
views/site/stats.erb
Normal file
|
@ -0,0 +1,342 @@
|
|||
|
||||
<div class="header-Outro with-columns">
|
||||
<div class="row content">
|
||||
<div class="col col-100">
|
||||
<h3>Site Statistics</h3>
|
||||
|
||||
<div class="feed-filter">
|
||||
<% if !@events.empty? && (site.followings_dataset.count > 0) %>
|
||||
<a href="/" <% if params[:activity].nil? %>class="selected"<% end %>>All</a>
|
||||
<a href="/?activity=mine" <% if params[:activity] == 'mine' %>class="selected"<% end %>>Profile Activity</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container news-feed">
|
||||
<div class="content misc-page columns right-col">
|
||||
<div class="col-left">
|
||||
<div class="col col-66">
|
||||
<!--
|
||||
<div class="row">
|
||||
|
||||
|
||||
<div class="col col-100 globe">
|
||||
<div id="earth_div"></div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="col col-50" style="padding-right: 0;">
|
||||
<table class="table table-striped" id="latest-visitors">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">San Francisco, CA</span>
|
||||
<a class="referrer" href=""><i class="fa fa-link"></i> neocities.org</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">Portland, OR</span>
|
||||
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM</span>
|
||||
<span class="paths"><a href="">/index</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">London, UK</span>
|
||||
<a class="referrer" href=""><i class="fa fa-link"></i> Twitter URL</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/about</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">Hong Kong, China</span>
|
||||
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">San Francisco, CA</span>
|
||||
<a class="referrer" href=""><i class="fa fa-link"></i> Facebook URL</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM - 4/27/15</span>
|
||||
<span class="paths"><a href="">/index</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">San Francisco, CA</span>
|
||||
<a class="referrer" href=""><i class="fa fa-link"></i> neocities.org</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM - 4/27/15</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a>, <a href="">/about</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">Portland, OR</span>
|
||||
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM - 4/27/15</span>
|
||||
<span class="paths"><a href="">/index</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">London, UK</span>
|
||||
<a class="referrer" href=""><i class="fa fa-link"></i> Twitter URL</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM - 4/27/15</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/about</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">Hong Kong, China</span>
|
||||
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM - 4/27/15</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a>, <a href="">/tech</a>, <a href="">/about</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="location">Hong Kong, China</span>
|
||||
<a class="referrer" href=""><i class="fa fa-search"></i> Google search</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="time">7:11PM - 4/27/15</span>
|
||||
<span class="paths"><a href="">/index</a>, <a href="">/links</a>, <a href="">/art</a>, <a href="">/music</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
-->
|
||||
|
||||
<h2>Total Visitors <small>last 7 days</small></h2>
|
||||
|
||||
<% if current_site && current_site.id == @site.id %>
|
||||
|
||||
<% if current_site.supporter? %>
|
||||
<ul class="nav h-Nav">
|
||||
<li><a href="?days=30">30 days</a></li>
|
||||
<li><a href="?days=90">90 days</a></li>
|
||||
<li><a href="?days=365">1 year</a></li>
|
||||
<li><a href="?days=sincethebigbang">All time</a></li>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>(<a href="/plan">Upgrade</a> to see up to see stats for all time)</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<canvas id="myChart" style="width:100%;height:300px;display:block"></canvas>
|
||||
|
||||
<!--
|
||||
<div class="row">
|
||||
<div class="col col-50">
|
||||
<h2>Top Paths <small>last 7 days</small></h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<th>Visits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @stats[:paths].each do |path| %>
|
||||
<tr>
|
||||
<td><a href="<%= @site.uri+path.name %>" target="_blank"><%= path.name.gsub(/\?.+/i, '') %></a></td>
|
||||
<td><%= path.views %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col col-50">
|
||||
<h2>Top Locations <small>last 7 days</small></h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<th>Visits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @stats[:locations].each do |location| %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= location[:name] %>
|
||||
</td>
|
||||
<td><%= location[:views] %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if current_site && current_site.id == @site.id %>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-100">
|
||||
<h2>Top Referrers</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Referrer</th>
|
||||
<th>Visits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @stats[:referrers].each do |referrer| %>
|
||||
<tr>
|
||||
<td><%= referrer.url %></td>
|
||||
<td><%= referrer.views %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="col col-33">
|
||||
<div class="news-site-info">
|
||||
<p class="site-url"><a href="<%= @site.uri %>" target="_blank"><%= @site.title %></a></p>
|
||||
<div class="stats">
|
||||
<div class="col col-50">
|
||||
<% if site.updated_at %>
|
||||
Last updated<br><strong><%= site.updated_at.ago %></strong>
|
||||
<% else %>
|
||||
Your new website!<br><strong><a href="/dashboard"><i class="fa fa-edit" title="Edit"></i> Start Building</a></strong>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="col col-50">
|
||||
<div><strong><%= site.views.format_large_number %></strong> views</div>
|
||||
<% follows_count = site.follows_dataset.count %>
|
||||
<div><strong><%= follows_count.format_large_number %></strong> follower<%= follows_count == 1 ? '' : 's' %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="<%= site.uri %>" class="large-portrait" style="background-image:url(<%= site.screenshot_url('index.html', '540x405') %>);"></a>
|
||||
|
||||
<div class="news-profile-button">
|
||||
<a href="/site/<%= site.username %>" class="btn-Action"><i class="fa fa-user"></i> Profile</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <script src="//www.webglearth.com/v2/api.js"></script> -->
|
||||
<script src="/js/Chart.min.js"></script>
|
||||
<script>
|
||||
//OpenGL globe
|
||||
$(document).ready(function() {
|
||||
/*
|
||||
var options = {
|
||||
sky: true,
|
||||
atmosphere: false,
|
||||
dragging: true,
|
||||
tilting: true,
|
||||
center: [46.8011, 8.2266],
|
||||
zoom: 2
|
||||
}
|
||||
|
||||
var earth = new WE.map('earth_div', options)
|
||||
earth.setView([20, -100], 2.07)
|
||||
|
||||
// WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
||||
// attribution: '© OpenStreetMap'
|
||||
//}).addTo(earth);
|
||||
|
||||
WE.tileLayer('http://data.webglearth.com/natural-earth-color/{z}/{x}/{y}.jpg', {
|
||||
tileSize: 256,
|
||||
bounds: [[-85, -180], [85, 180]],
|
||||
minZoom: 0,
|
||||
maxZoom: 16,
|
||||
attribution: 'WebGL Earth Tiles',
|
||||
tms: true
|
||||
}).addTo(earth)
|
||||
|
||||
<% @stats[:locations].each do |location| %>
|
||||
var marker = WE.marker([<%= location[:latitude] %>, <%= location[:longitude] %>]).addTo(earth);
|
||||
marker.bindPopup("<b><%= location[:name] %></b><br><%= location[:views] %> views", {maxWidth: 150, closeButton: true})
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
// Start a simple rotation animation
|
||||
var before = null
|
||||
requestAnimationFrame(function animate(now) {
|
||||
var c = earth.getPosition()
|
||||
var elapsed = before? now - before: 0
|
||||
before = now
|
||||
earth.setCenter([c[0], c[1] + 0.1*(elapsed/30)])
|
||||
requestAnimationFrame(animate)
|
||||
});
|
||||
*/
|
||||
|
||||
//chart.js
|
||||
var data = {
|
||||
labels: <%== @stats[:stat_days].collect {|s| s.created_at.strftime("%b %-d")}.to_json %>,
|
||||
datasets: [
|
||||
{
|
||||
label: "Hits",
|
||||
fillColor: "rgba(220,220,220,0.2)",
|
||||
strokeColor: "rgba(220,220,220,1)",
|
||||
pointColor: "rgba(220,220,220,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
pointHighlightFill: "#fff",
|
||||
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||
data: <%== @stats[:stat_days].collect {|s| s.hits}.to_json %>
|
||||
},
|
||||
{
|
||||
label: "Unique Visits",
|
||||
fillColor: "rgba(151,187,205,0.2)",
|
||||
strokeColor: "rgba(151,187,205,1)",
|
||||
pointColor: "rgba(151,187,205,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
pointHighlightFill: "#fff",
|
||||
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||
data: <%== @stats[:stat_days].collect {|s| s.views}.to_json %>
|
||||
}
|
||||
]
|
||||
}
|
||||
// Get context with jQuery - using jQuery's .get() method.
|
||||
var ctx = $("#myChart").get(0).getContext("2d")
|
||||
// This will get the first returned node in the jQuery collection.
|
||||
//var myNewChart = new Chart(ctx);
|
||||
var myLineChart = new Chart(ctx).Line(data, {
|
||||
bezierCurve: false,
|
||||
multiTooltipTemplate: "<%== @multi_tooltip_template %>"
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -13,6 +13,7 @@
|
|||
<li>JavaScript (.js, .json, .geojson)</li>
|
||||
<li>CSS (.css)</li>
|
||||
<li>Text (.txt, .text, .csv, .tsv)</li>
|
||||
<li>XML (.xml)</li>
|
||||
<li>Web Fonts (.eot, .ttf, .woff, .woff2, .svg)</li>
|
||||
<li>MIDI Files (.mid, .midi)</li>
|
||||
</ul>
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
<% end %>
|
||||
</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 %>
|
||||
<input name="dir" type="hidden" value="<%= params[:dir] %>">
|
||||
<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>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>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue